On 28/03/2022 15:52, Jørgen Kvalsvik wrote:
> Hello,
>
> I have made some changes based on the review feedback, please have a look. In
> summary:
>
> * Support for -profile=atomic
> * gcc -fprofile-conditions does not ICE without -fprofile-arcs
> * Indentation and formatting complies with contrib/GNU_style_check
> * Some small adjustment to documentation, phrasing
>
> As mentioned in the previous message, I think it should only be necessary to do
> atomic fetch/store on the global accumulators, not the locals (as they are
> specific to the activation record). If is wrong then I have no problems
> extending the atomics to the function-local accumulators too, please let me
> know. I added a simple test case for -profile=update, gcov-20.c.
>
> I've added checks for profile_condition_flag everywhere the profile_arc_flag is
> checked, as they share the same code paths and infrastructure. I also made it so
> that -fprofile-conditions is sufficient for linking gcov.
>
> gcc will no longer crash on -fprofile-conditions -fprofile-generate by
> explicitly checking coverage_begin_*. I'm not too happy with how it turned out,
> but the condition coverage needs a lot more analysis before it can write out its
> gcov structure, which doesn't fit with the flow of branch_prob ().
>
> Thanks,
> Jørgen
>
> On 25/03/2022 20:44, Jørgen Kvalsvik wrote:
>> Hello, and thanks for the review!
>>
>>> 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?
>>
>> Sort-of. It is not really aware of sub-expressions at all, but tracks every
>> boolean condition/term in the full expression, mapped to a bitvector. I usually
>> find it easier to understand visually:
>>
>> if (a || (b && c) || d)
>>
>> terms | a b c d
>> ------|--------
>> true | 0 0 0 0
>> false | 0 0 0 0
>>
>> Whenever a = true, true becomes 1000 and false remains 0000. a = true would
>> short-circuit, (implemented as a normal edge to the "exit" node of the
>> expression) leaving bcd unevaluated. Coverage is then determined as the number
>> of unset bits in this vector. The accumulators are zero'd on every function
>> entry, and |= into the global "counter" on function exit.
>>
>>> 2) As mentioned these are bit sets, can you please explain why you (&
>>> -value) from these sets?
>>
>> Yes, excellent question. This should answer 3) too, and is the most surprsing
>> thing when unfamiliar with MC/DC.
>>
>> For modified condition/decision coverage we need to prove that a condition's
>> value independently affects the outcome/decision. In other words, would
>> flipping a condition change the outcome?
>>
>> Let's take your if (a || b) check, which says that 1/4 conditions are covered
>> on (0, 1). If a = 1, the expression is always true, and b is never evaluated.
>> In fact, the value of b does not change the outcome at all, so intuitively only
>> 1/4 of the conditions are covered.
>>
>> On (0, 1) b gets evaluated, so in a way we know that a = 0. However, the value
>> of a will no longer affect the outcome because (* || 1) is always true. In a
>> way, you can think of it as reverse short-circuiting, the same thing would
>> happen on (* && 0). This is what Wahlen, Heimdahl, and De Silva calls masking.
>> What I do is figure out when masking must happen by analyzing the CFG and zero
>> out the bits that are masked (i.e. no longer affect the outcome). This is in
>> practice what happens to the bits when regular short circuiting happen.
>>
>> So while (0, 1) "sees" the condition a = false, it cannot prove that it
>> mattered based on your inputs. In general, you need N+1 test cases to cover N
>> conditionals with MC/DC. As a consequence, the only way of covering a = false
>> is (0, 0), which alone would be 2/4 cases covered.
>>
>> I hope this made it clearer, if not I can write a more elaborate explanation
>> with more examples.
>>
>>> 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?
>>
>> My paper is not written yet, but I will describe the high-level algorithm (+
>> extracts from source comments) at the end of this email, as it is a bit long.
>>
>>> 5) I noticed the following ICE:
>> Yes, this is an unfortunate flaw - I piggyback on the setup done by branch
>> coverage, which means I silently assume --coverage is used. I was unsure what
>> to do (and meant to ask, sorry!) as it is a larger decision - should
>> condition-coverage also imply branch coverage? Should condition coverage work
>> on its own? Other options?
>>
>> I'm happy to implement either strategy - please advise on what you would
>> prefer.
>>
>>
>>> 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.
>>
>> Sure thing, I'll add some tabs. All the helper functions are already static (in
>> an unnamed namespace), but I can remove the namespace and/or add static.
>>
>>> 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).
>> Will do! I always intended to support the atomics, not having them there is an
>> oversight. To confirm, I will only need to use atomics for the global counters,
>> right? The per-call accumulators are local to each activation record and should
>> not need the atomics, or am I missing something?
>>
>> ALGORITHM
>>
>> Phase 1: expression isolation from CFG analysis
>>
>> Break the CFG into smaller pieces ("sections") by splitting it on dominators.
>> No expression can cross a dominator, so becomes a natural way of terminating
>> expression searches.
>>
>> For each section, do a BFS from the root node and collect all reachable nodes
>> following edges that point to "conditional" nodes, i.e. nodes with outgoing
>> true&false edges. Series of single-parent single-exit nodes are contracted.
>> This gives a good estimate for an expression, but might include conditions in
>> the if/else blocks.
>>
>> The algorithm then collects the immediate neighborhood, i.e. all nodes adjacent
>> to the collected set, dropping everything in the set itself. Then, a new BFS is
>> performed, but now "upwards" (i.e. following predecessors), skipping any
>> neighbor not dominated by the root (this eliminates loops and other expressions
>> with the same "exit" block, see BFS-up). The intersection of all the paths
>> found with this BFS is the isolated expression.
>>
>> If the root is not a conditional, it is skipped and it's successor
>> becomes the new root. If an expression is found it has a pair of "expression
>> sucessors", and the algorithm recurses into both, evaluating this sub-section
>> in isolation, i.e.
>>
>> if (a || b) {
>> root:
>> if (c && d) {
>> ...
>> }
>> ...
>> sink:
>> }
>>
>> The sections between the root and sink labels are evaulated as if the outer
>> expression did not exist.
>>
>> From the comments:
>>
>> 1. Collect all nodes reachable from the root node through (contracted) paths
>> of true/false edges.
>> 2. Collect the neighbors of the reachable node (set).
>> 3. From every node in the neighborhood, walk the up the CFG and mark every
>> reachable node. Only the nodes reachable from *every* node in the
>> neighborhood are a part of the first expression.
>> 4. Record the expression plus the two successors of the last (highest-index)
>> node in the expression, i.e. the last term.
>> 5. Repeat using the two successors as new root nodes.
>>
>> It is not guaranteed to find nodes in the order of the expression, i.e. it
>> might find (a || b) && c as [a c b], so the output is sorted by
>> basic_block->index.
>>
>> Steps 2 and 3 are necessary to distinguish chained conditionals from
>> multi-term conditionals, e.g. to separate
>>
>> if (a)
>> {
>> if (b)
>> work ();
>> }
>> if (a && b)
>> work ();
>>
>>
>> On BFS-up:
>>
>> Either block of a conditional could have more decisions and loops, so
>> isolate the first decision by set-intersecting all paths from the
>> post-dominator to the entry block.
>>
>> The function returns the number of blocks from n that make up the leading
>> expression in prefix order (i.e. the order expected by the instrumenting
>> code). When this function returns 0 there are no decisions between pre and
>> post, and this segment of the CFG can safely be skipped.
>>
>> The post nodes can have predecessors that do not belong to this subgraph,
>> which are skipped. This is expected, for example when there is a
>> conditional in the else-block of a larger expression:
>>
>> if (A)
>> {
>> if (B) {}
>> } else {
>> if (C) {} else {}
>> }
>>
>> A
>> t / \ f
>> / \
>> B C
>> /\ / \
>> / \ T F
>> T \ \ /
>> \ | o
>> \ | /
>> \ | /
>> \|/
>> E
>>
>> Processing [B, E) which looks like:
>>
>> B
>> /|
>> / |
>> T |
>> \ /
>> E ----> o // predecessor outside [B, e)
>>
>>
>> Phase 2: Masking vector and instrumentation
>>
>> The input to this phase is the basic blocks that make up every expression, plus
>> its two exit nodes, i.e. the 3-term expression (a || b || c) yields the input
>> [a b c T F] to phase 2.
>>
>> For every expression, allocate two local accumulators (bit-vectors), one for
>> the true-vector, one for the false-vector. These are zero'd on function entry
>> and flushed on function exit.
>>
>> For each "conditional" block (a b c), unconditionally set the corresponding bit
>> when an edge in the CFG is taken. Rough pseudo-gimple illustration:
>>
>> if (a) {
>> accu_true |= 1 << 0
>> goto done;
>> } else {
>> accu_false |= 1 << 0
>> if (b) {
>> accu_true |= 1 << 1;
>> goto done;
>> } else {
>> accu_false |= 1 << 1;
>> ...
>> }
>> }
>>
>> global_true[expr_n] |= accu_true
>> global_false[expr_n] |= accu_false
>>
>> This alone would more-or-less be branch coverage, but would not prove that
>> terms would independently decide the outcome. To find masking, the expression
>> is walked in search of triangles. A || B as a decision diagram becomes
>>
>> A
>> t|\f
>> | \
>> | B
>> |t/ \f
>> |/ \
>> T F
>>
>> and we search for triangles like ATB, that is nodes with 2 predecessors with
>> the same boolean value, where the two predecessors are connected with the
>> opposite value. This marks a candidate for masking, and the terms masked are
>> determined by chasing predecessors starting at the top of the triangle for as
>> long as the chain of predecessors have the same truth value as the "exit"
>> edges. This set is often empty.
>>
>> The edge that triggers masking is then instrumented with a accu &= mask which
>> wipes out anything that does not independently affect the outcome. Note that
>> nothing is ever wiped from the global accumulator, only the local. The (a || b)
>> example had a false vector of [1, 0] at some point, but it was wiped by the
>> masking operation (b = 1 is the only masking value for a || b).
>>
>> From the comments:
>>
>> Scan the blocks that make up an expression and look for conditions that
>> would mask other conditions. For a deeper discussion on masking, see
>> Whalen, Heimdahl, De Silva "Efficient Test Coverage Measurement for MC/DC".
>> Masking is best illustrated with an example:
>>
>> A || B. If B is true then A will does not independently affect the
>> decision. In a way, this is "reverse" short circuiting, and always work on
>> the right-hand side of expressions. * || true is always true and * &&
>> false is always false - the left-hand-side does not affect the outcome, and
>> their values should not contribute to modified condition/decision coverage.
>>
>> A || B interpreted as a decision diagram becomes:
>>
>> A
>> t|\f
>> | \
>> | B
>> |t/ \f
>> |/ \
>> T F
>>
>> The algorithm looks for triangles like ATB. Masking right-hand sides happen
>> when a block has a pair of incoming edges of the same boolean value, and
>> there is an edge connecting the two predecessors with the *opposite* boolean
>> value. The masking block is B, and the masked block is A. In this
>> particular example:
>>
>> Masking can affect "outside" its own subexpression; in A || (B && C) if C is
>> false, B is masked. However, if (B && C) is true, A gets masked. B && C
>> can be determined from evaluating C since !B would short-circuit, so a true
>> C would mask A.
>>
>> A
>> t|\f
>> | \
>> | \
>> | \
>> | B
>> | t/ \f
>> | C |
>> |t/ \f |
>> |/ \ |
>> T F
>>
>> Notice how BFC forms a triangle. Expressions masked by an edge are
>> determined by:
>> * Go to the predecessor with truth value b (if it exists).
>> * Follow the path of ancestors with taking only !b edges, recording every
>> node from here on.
>>
>> For the case where C can mask A, the path goes C [true]-> B -> [false] A, so
>> C [true] masks A.
>>
>> The mask is output as a bit-set stored in a gcov_type_unsigned. The
>> bit-sets are output in pairs, one for each decision (the outcome of the
>> boolean expression, or which arc to take in the CFG).
>>
>> Phase 3: reading the report
>>
>> This is pretty standard gcov stuff, except the "counters" are interpreted as
>> bitsets, and the number of terms in the expressions is stored. Coverage then
>> becomes popcount / expected.
>>
>> Final remarks:
>>
>> Granted, this is a bit dense, but I think the overall approach is easy to grasp
>> when supported by a lot of example graphs and stepping. I will try to type up a
>> bunch with the paper and publish, but for now all I have is the code (which I
>> hope for the most part is easy to follow). I'll update this with style updates
>> and atomics support, but I would prefer a maintainer to make the call if this
>> condition coverage should imply branch coverage, or if it should work without it.
>>
>> Thanks,
>> Jørgen
>>
>> On 24/03/2022 17:08, Martin Liška via Gcc-patches wrote:
>>> 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 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
>>>>
>>>> --
>>>>
>>>> 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
>>
... And with another tiny change that fixes Martin's while (1); case.