public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
From: "Jørgen Kvalsvik" <j@lambda.is>
To: Jan Hubicka <hubicka@ucw.cz>
Cc: gcc-patches@gcc.gnu.org, richard.guenther@gmail.com
Subject: Re: [PATCH v9 1/2] Add condition coverage (MC/DC)
Date: Thu, 22 Feb 2024 15:24:32 +0100	[thread overview]
Message-ID: <840506d1-b85d-4e68-b2e2-fa8e329485e8@lambda.is> (raw)
In-Reply-To: <ZddLcCGDl_XC7dQb@kam.mff.cuni.cz>

On 22/02/2024 14:26, Jan Hubicka wrote:
> Hello,
>> This patch adds support in gcc+gcov for modified condition/decision
>> coverage (MC/DC) with the -fcondition-coverage flag. MC/DC is a type of
>> test/code coverage and it is particularly important for safety-critical
>> applicaitons in industries like aviation and automotive. Notably, MC/DC
>> 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
>>
>> MC/DC comes in different flavours, the most important being unique
>> cause MC/DC and masking MC/DC - this patch implements masking MC/DC,
>> which is works well with short circuiting semantics, and according to
>> John Chilenski's "An Investigation of Three Forms of the Modified
>> Condition Decision Coverage (MCDC) Criterion" (2001) is as good as
>> unique cause at catching bugs.
>>
>> Whalen, Heimdahl, and De Silva "Efficient Test Coverage Measurement for
>> MC/DC" describes an algorithm for determining masking from an AST walk,
>> but my algorithm figures this out from analyzing the control flow graph.
>> The CFG is considered a binary decision diagram and an evaluation
>> becomes a path through the BDD, which is recorded. Certain paths will
>> mask ("null out") the contribution from earlier path segments, which can
>> be determined by finding short circuit endpoints. Masking is the
>> short circuiting of terms in the reverse-ordered Boolean function, and
>> the masked terms do not affect the decision like short-circuited
>> terms do not affect the decision.
>>
>> A tag/discriminator mapping from gcond->uid is created during
>> gimplification and made available through the function struct. The
>> values are unimportant as long as basic conditions constructed from a
>> single Boolean expression are given the same identifier. This happens in
>> the breaking down of ANDIF/ORIF trees, so the coverage generally works
>> well for frontends that create such trees.
>>
>> Like Whalen et al this implementation records coverage in fixed-size
>> bitsets which gcov knows how to interpret. This takes only a few bitwise
>> operations per condition and is very fast, but comes with a limit on the
>> number of terms in a single boolean expression; the number of bits in a
>> gcov_unsigned_type (which is usually typedef'd to uint64_t). For most
>> practical purposes this is acceptable, and by default a warning will be
>> issued if gcc cannot instrument the expression.  This is a practical
>> limitation in the implementation, not the algorithm, so support for more
>> conditions can be added by also introducing arbitrary-sized bitsets.
>>
>> 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 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
>>          ]
>>      }
>> ],
>>
>> Expressions with constants may be heavily rewritten before it reaches
>> the gimplification, so constructs like int x = a ? 0 : 1 becomes
>> _x = (_a == 0). From source you would expect coverage, but it gets
>> neither branch nor condition coverage. The same applies to expressions
>> like int x = 1 || a which are simply replaced by a constant.
>>
>> The test suite contains a lot of small programs and 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.
>>
>> gcc/ChangeLog:
>>
>> 	* builtins.cc (expand_builtin_fork_or_exec): Check
>> 	  condition_coverage_flag.
>> 	* collect2.cc (main): Add -fno-condition-coverage to OBSTACK.
>> 	* common.opt: Add new options -fcondition-coverage and
>> 	  -Wcoverage-too-many-conditions.
>> 	* doc/gcov.texi: Add --conditions documentation.
>> 	* doc/invoke.texi: Add -fcondition-coverage documentation.
>> 	* function.cc (allocate_struct_function): Init cond_uids.
>> 	* function.h (struct function): Add cond_uids.
>> 	* gcc.cc: Link gcov on -fcondition-coverage.
>> 	* gcov-counter.def (GCOV_COUNTER_CONDS): New.
>> 	* gcov-dump.cc (tag_conditions): New.
>> 	* gcov-io.h (GCOV_TAG_CONDS): New.
>> 	(GCOV_TAG_CONDS_LENGTH): New.
>> 	(GCOV_TAG_CONDS_NUM): New.
>> 	* 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 condition counters.
>> 	(read_count_file): Likewise.
>> 	(file_summary): Print conditions.
>> 	(accumulate_line_info): Accumulate conditions.
>> 	(output_line_details): Print conditions.
>> 	* gimplify.cc (next_cond_uid): New.
>> 	(reset_cond_uid): New.
>> 	(shortcut_cond_r): Set condition discriminator.
>> 	(tag_shortcut_cond): New.
>> 	(shortcut_cond_expr): Set condition discriminator.
>> 	(gimplify_cond_expr): Likewise.
>> 	(gimplify_function_tree): Call reset_cond_uid.
>> 	* ipa-inline.cc (can_early_inline_edge_p): Check
>> 	  condition_coverage_flag.
>> 	* ipa-split.cc (pass_split_functions::gate): Likewise.
>> 	* passes.cc (finish_optimization_passes): Likewise.
>> 	* profile.cc (struct condcov): New declaration.
>> 	(cov_length): Likewise.
>> 	(cov_blocks): Likewise.
>> 	(cov_masks): Likewise.
>> 	(cov_maps): 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-core.h (struct tree_exp): Add condition_uid.
>> 	* tree-profile.cc (struct conds_ctx): New.
>> 	(CONDITIONS_MAX_TERMS): New.
>> 	(EDGE_CONDITION): New.
>> 	(topological_cmp): New.
>> 	(index_of): New.
>> 	(single_p): New.
>> 	(single_edge): New.
>> 	(contract_edge_up): New.
>> 	(struct outcomes): New.
>> 	(conditional_succs): New.
>> 	(condition_index): New.
>> 	(condition_uid): New.
>> 	(masking_vectors): New.
>> 	(emit_assign): New.
>> 	(emit_bitwise_op): New.
>> 	(make_top_index_visit): New.
>> 	(make_top_index): New.
>> 	(paths_between): New.
>> 	(struct condcov): New.
>> 	(cov_length): New.
>> 	(cov_blocks): New.
>> 	(cov_masks): New.
>> 	(cov_maps): New.
>> 	(cov_free): New.
>> 	(find_conditions): New.
>> 	(struct counters): New.
>> 	(find_counters): New.
>> 	(resolve_counter): New.
>> 	(resolve_counters): New.
>> 	(instrument_decisions): New.
>> 	(tree_profiling): Check condition_coverage_flag.
>> 	(pass_ipa_tree_profile::gate): Likewise.
>> 	* tree.h (SET_EXPR_UID): New.
>> 	(EXPR_COND_UID): New.
>>
>> libgcc/ChangeLog:
>>
>> 	* libgcov-merge.c (__gcov_merge_ior): New.
>>
>> 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.
>> 	* gcc.misc-tests/gcov-22.c: New test.
>> 	* gcc.misc-tests/gcov-23.c: New test.
> 
>> diff --git a/gcc/function.cc b/gcc/function.cc
>> index 89841787ff8..e57d0488c7a 100644
>> --- a/gcc/function.cc
>> +++ b/gcc/function.cc
>> @@ -4793,6 +4793,7 @@ allocate_struct_function (tree fndecl, bool abstract_p)
>>   
>>     cfun = ggc_cleared_alloc<function> ();
>>   
>> +  cfun->cond_uids = hash_map <gcond*, unsigned>::create_ggc ();
> 
> There are a lot of struct functions created during WPA ICF stage, where
> this hash will be completely unused.  I think you can initialize it to
> NULL here, allocate it at the gimplification time only and also
> deallocate in free_after_compilation.
> 
> It would be good to arrange the patch to do nothing unless the condition
> coverage command line option is used.  At the moment it seems you
> compute conditional uids just to ignore them later.
> 
> Also at the moment the hash will dangle pointers to statements that was
> optimized out. There are two options. Either initialize it with
> GTY((cache)) which will make garbage collector to remove entries to
> statements not reachable otherwise.

I wonder, is that not already handled by using gcond_cache_map_traits 
template parameter?

   struct gcond_cache_map_traits 
 

   : simple_cache_map_traits <ggc_cache_ptr_hash <gcond>, unsigned>
   {
   };

Of course, if the same thing can be accomplished by GTY((cached)) that 
is much neater.

I have addressed the rest of the review, and will publish another draft 
soon.

> 
> Other option would be move it out of gabrage collector completely and
> extend gsi_remove to also remove entry in the hash just as it does so
> This should also make it possible to move it out of ggc.
> for EH (see calls to remove_stmt_from_eh_lp).
>> @@ -134,6 +136,33 @@ public:
>>     vector<unsigned> lines;
>>   };
>>   
>> +/* Describes a single conditional expression and the (recorded) conditions
>> +   shown to independently affect the outcome.  */
>> +class condition_info
>> +{
>> +public:
>> +  condition_info ();
>> +
>> +  int popcount () const;
>> +
>> +  /* Bitsets storing the independently significant outcomes for true and false,
>> +   * respectively.  */
> Extra * here.
>> +  gcov_type_unsigned truev;
>> +  gcov_type_unsigned falsev;
>> +
>> +  /* Number of terms in the expression; if (x) -> 1, if (x && y) -> 2 etc.  */
>> +  unsigned n_terms;
>> +};
> 
>> diff --git a/gcc/gimplify.cc b/gcc/gimplify.cc
>> index 342e43a7f25..591cb50193c 100644
>> --- a/gcc/gimplify.cc
>> +++ b/gcc/gimplify.cc
>> @@ -71,6 +71,24 @@ along with GCC; see the file COPYING3.  If not see
>>   #include "context.h"
>>   #include "tree-nested.h"
>>   
> By coding style there should be acomment what nextuid is used for.
>> +static unsigned nextuid = 1;
>> +/* Get a fresh identifier for a new condition expression.  This is used for
>> +   condition coverage.  */
>> +static unsigned
>> +next_cond_uid ()
>> +{
>> +    return nextuid++;
>> +}
>> +/* Reset the condition uid to the value it should have when compiling a new
>> +   function.  0 is already the default/untouched value, so start at non-zero.
>> +   A valid and set id should always be > 0.  This is used for condition
>> +   coverage.  */
>> +static void
>> +reset_cond_uid ()
>> +{
>> +    nextuid = 1;
>> +}
>> +
>>   /* Hash set of poisoned variables in a bind expr.  */
> 
>> +/* Given a multi-term condition (ANDIF, ORIF), walk the predicate and tag every
>> +   term with uid.  When two basic conditions share the uid discriminator when
>> +   they belong to the same predicate, which used by the condition coverage.
>> +   Doing this as an explicit step makes for a simpler implementation than
>> +   weaving it into the splitting code as the splitting code eventually reaches
>> +   back up to gimplfiy_expr which makes bookkeeping complicated.  */
> 
> Coding style also asks to document parameters (and reffer to them in
> uppercase, like PRED/CONDITION_UID)
>> +static void
>> +tag_shortcut_cond (tree pred, unsigned condition_uid)
>> +{
>> +  if (TREE_CODE (pred) == TRUTH_ANDIF_EXPR
>> +      || TREE_CODE (pred) == TRUTH_ORIF_EXPR)
>> +    {
>> +      tree fst = TREE_OPERAND (pred, 0);
>> +      tree lst = TREE_OPERAND (pred, 1);
>> +
>> +      if (TREE_CODE (fst) == TRUTH_ANDIF_EXPR
>> +	  || TREE_CODE (fst) == TRUTH_ORIF_EXPR)
>> +	tag_shortcut_cond (fst, condition_uid);
>> +      else if (TREE_CODE (fst) == COND_EXPR)
>> +	SET_EXPR_UID (fst, condition_uid);
>> +
>> +      if (TREE_CODE (lst) == TRUTH_ANDIF_EXPR
>> +	  || TREE_CODE (lst) == TRUTH_ORIF_EXPR)
>> +	tag_shortcut_cond (lst, condition_uid);
>> +      else if (TREE_CODE (lst) == COND_EXPR)
>> +	SET_EXPR_UID (lst, condition_uid);
>> +    }
>> +}
>>   /* Given a conditional expression EXPR with short-circuit boolean
>>      predicates using TRUTH_ANDIF_EXPR or TRUTH_ORIF_EXPR, break the
>>      predicate apart into the equivalent sequence of conditionals.  */
>>   
>>   static tree
>> -shortcut_cond_expr (tree expr)
>> +shortcut_cond_expr (tree expr, unsigned condition_uid)
> Similarly here CONDITION_UID should be documented.
> 
>> +
>> +/* Compare two basic blocks by their order in the expression i.e. for (a || b)
>> +   then topological_cmp (a, b, ...) < 0.  The result is undefined if lhs, rhs
>> +   belong to different expressions.  The top_index argument should be the
>> +   top_index vector from ctx.  */
> Please use LHS/RHS and TOP_INDEX in commants (in all functions you aded).
> It also seems that you are not releasing the hash after its use.  It
> leaks points to gimple statments that are removed, so it may
> 
> The patch look OK to me with the hash memory management change above.
> I really do apologize for being so slow on the reviews - it is an
> interesting feature and I should have handled it faster.
> 
> Honza


      reply	other threads:[~2024-02-22 14:24 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-12-31 15:51 Jørgen Kvalsvik
2023-12-31 15:51 ` [PATCH v9 2/2] Add gcov MC/DC tests for GDC Jørgen Kvalsvik
2023-12-31 22:06   ` Iain Buclaw
2024-02-22 13:27   ` Jan Hubicka
2024-01-02 21:07 ` [PATCH v9 1/2] Add condition coverage (MC/DC) Jørgen Kvalsvik
2024-01-09  9:04   ` Ping: " Jørgen Kvalsvik
2024-01-22 10:21     ` Ping^2 " Jørgen Kvalsvik
2024-01-29  8:53       ` Ping^3 " Jørgen Kvalsvik
2024-02-06 13:09         ` Ping^4 " Jørgen Kvalsvik
2024-02-21 19:08           ` Ping^5 " Jørgen Kvalsvik
2024-01-29 23:31     ` Ping: " Fangrui Song
2024-01-30  9:33       ` Jørgen Kvalsvik
2024-02-22 13:26 ` Jan Hubicka
2024-02-22 14:24   ` Jørgen Kvalsvik [this message]

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=840506d1-b85d-4e68-b2e2-fa8e329485e8@lambda.is \
    --to=j@lambda.is \
    --cc=gcc-patches@gcc.gnu.org \
    --cc=hubicka@ucw.cz \
    --cc=richard.guenther@gmail.com \
    /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).