An inlined function with conditions had every call site add to the
size of the counter in the report for the function itself, so that
this function called twice ended up with a report that says 8 outcomes
for an expression that clearly has 4 (2 conditions).
__attribute__((always_inline))
inline int
inline_with_cond1 (int a)
{
if (a < 10 && a >= 0)
return a;
else
return a+2;
}
#####: 7: if (a < 10 && a >= 0)
condition outcomes covered 0/8
When the function is inlined we copy the basic block and tweak the
condition uids (which associate the basic condition with its
expression and informs the coverage pass about which blocks to tie
into the same decision), but this breaks when a function is called
inlined multiple times or from multiple places.
This patch moves the mapping up a level to where the whole function is
inlined and we process the whole inlined CFG as a unit every time the
function is inlined. Since the inlined function uses internally
consistent uids and are processed completely and independently we can
simply shift either uid by the highest uid in the calling function.
The result is that we get the expected counts:
#####: 7: if (a < 10 && a >= 0)
condition outcomes covered 0/4
The inlined function is also counted at call site:
#####: 50: if (a < b
condition outcomes covered 0/4
#####: 51: || inline_with_cond1 (a) < inline_with_cond2 (b))
condition outcomes covered 0/4
This is a side effect of the function body being copy-pasted into the
caller. With always_inline being a rather aggressive guide this is
probably appropriate as the inlined conditions are being evaluated in
the function proper.
---
Bootstrapped and tested on x86_64-linux-gnu.
gcc/ChangeLog:
* gimplify.cc (gimple_associate_condition_with_expr): Make
non-static.
* gimplify.h (gimple_associate_condition_with_expr): Declare
it.
* tree-inline.cc (copy_bb): Remove conditional expr uid
handling.
(next_free_cond_uid): New function.
(rebase_condition_uids): New function.
(copy_cfg_body): Remap conditional expr uids when inlining.
gcc/testsuite/ChangeLog:
* gcc.misc-tests/gcov-19.c: New test.
---
gcc/gimplify.cc | 2 +-
gcc/gimplify.h | 2 +
gcc/testsuite/gcc.misc-tests/gcov-19.c | 67 ++++++++++++++++++++++++++
gcc/tree-inline.cc | 67 +++++++++++++++++++++-----
4 files changed, 126 insertions(+), 12 deletions(-)
diff --git a/gcc/gimplify.cc b/gcc/gimplify.cc
index d521bda81863..0f0116b20971 100644
--- a/gcc/gimplify.cc
+++ b/gcc/gimplify.cc
@@ -5412,7 +5412,7 @@ generic_expr_could_trap_p (tree expr)
the same UID; 'if (a && b && c) { if (d || e) ... } ...' should yield the
{ a: 1, b: 1, c: 1, d: 2, e: 2 } when gimplification is done. This is used
for condition coverage. */
-static void
+void
gimple_associate_condition_with_expr (struct function *fn, gcond *stmt,
unsigned uid)
{
diff --git a/gcc/gimplify.h b/gcc/gimplify.h
index 18c7514c9a3e..13639fbdeb84 100644
--- a/gcc/gimplify.h
+++ b/gcc/gimplify.h
@@ -90,5 +90,7 @@ extern enum gimplify_status gimplify_va_arg_expr (tree *,
gimple_seq *,
gimple_seq *);
extern bool generic_expr_could_trap_p (tree expr);
gimple *gimplify_assign (tree, tree, gimple_seq *);
+extern void gimple_associate_condition_with_expr (struct function*, gcond*,
+ unsigned);
#endif /* GCC_GIMPLIFY_H */
diff --git a/gcc/testsuite/gcc.misc-tests/gcov-19.c
b/gcc/testsuite/gcc.misc-tests/gcov-19.c
index fb0400c20b9d..0049ae38fad4 100644
--- a/gcc/testsuite/gcc.misc-tests/gcov-19.c
+++ b/gcc/testsuite/gcc.misc-tests/gcov-19.c
@@ -1516,6 +1516,72 @@ mcdc027c (int a, int b) /* conditions(1/2) false(0) */
x = 10;
}
+__attribute__((always_inline))
+inline int
+inline_with_cond1 (int a)
+{
+ if (a < 10 && a >= 0) /* conditions(2/4; 2/4) false(0 1;0 1) */
+ /* conditions(end) */
+ return a;
+ else
+ return a+2;
+}
+
+__attribute__((always_inline))
+inline int
+inline_with_cond2 (int a)
+{
+ if (a < 10 && a >= 0) /* conditions(2/4; 2/4) false(0 1;0 1) */
+ /* conditions(end) */
+ return a;
+ else
+ return a+2;
+}
+
+int
+mcdc027d_inner (int a, int b)
+{
+ int y = 0;
+ if (a < b /* conditions(2/4; 2/4)
true(0 1;) false(;0 1) */
+ || inline_with_cond1 (a) < inline_with_cond1 (b))
+ /* conditions(end) */
+ y = a-b;
+ else
+ y = a+b;
+
+ if (a < b /* conditions(2/4; 2/4)
true(0 1;) false(;0 1) */
+ || inline_with_cond2 (a) < inline_with_cond2 (b))
+ /* conditions(end) */
+ y = a-b;
+ else
+ y = a+b;
+ return y;
+}
+
+/* An always_inline may be called multiple times from both the same function
+ and in multiple functions without counts being broken. The Boolean
+ expressions of the inlined functions will be included in the calling
+ function, too. */
+void
+mcdc027d (int a, int b)
+{
+ if (a < b /* conditions(2/4; 2/4)
true(0 1;) false(;0 1) */
+ || inline_with_cond1 (a) < inline_with_cond2 (b))
+ /* conditions(end) */
+ x = a-b;
+ else
+ x = a+b;
+
+ if (a < b /* conditions(2/4; 2/4)
true(0 1;) false(;0 1) */
+ || inline_with_cond2 (a) < inline_with_cond1 (b))
+ /* conditions(end) */
+ x = a-b;
+ else
+ x = a+b;
+
+ x += mcdc027d_inner (a, b);
+}
+
int main ()
{
mcdc001a (0, 1);
@@ -1773,6 +1839,7 @@ int main ()
mcdc027a (1);
mcdc027b (1);
mcdc027c (1, 0);
+ mcdc027d (1, 0);
}
/* { dg-final { run-gcov conditions { --conditions gcov-19.c } } } */
diff --git a/gcc/tree-inline.cc b/gcc/tree-inline.cc
index 7d73d5959073..315241ceef61 100644
--- a/gcc/tree-inline.cc
+++ b/gcc/tree-inline.cc
@@ -2107,17 +2107,6 @@ copy_bb (copy_body_data *id, basic_block bb,
if (gimple_nop_p (stmt))
continue;
- /* If -fcondition-coverage is used, register the inlined conditions
- in the cond->expression mapping of the caller. The expression tag
- is shifted conditions from the two bodies are not mixed. */
- if (id->src_cfun->cond_uids && is_a <gcond*> (stmt))
- {
- gcond *orig_cond = as_a <gcond*> (orig_stmt);
- gcond *cond = as_a <gcond*> (stmt);
- unsigned *v = id->src_cfun->cond_uids->get (orig_cond);
- if (v)
- cfun->cond_uids->put (cond, *v + min_cond_uid);
- }
gimple_duplicate_stmt_histograms (cfun, stmt, id->src_cfun,
orig_stmt);
@@ -3082,6 +3071,55 @@ redirect_all_calls (copy_body_data * id, basic_block bb)
}
}
+/* Find the next free conditional expression identifier for FN. The identifier
+ will be higher than all other entries for FN. This is used for condition
+ coverage (MC/DC). If condition coverage is not used this is effectively a
+ no-op. */
+static unsigned
+next_free_cond_uid (struct function *fn)
+{
+ unsigned val = 0;
+ if (fn->cond_uids)
+ for (auto itr : *fn->cond_uids)
+ if (itr.second >= val)
+ val = itr.second;
+ return val + 1;
+}
+
+/* Map the basic condition identifiers (uids) in for the SRC_BB in SRC_CFUN
+ to identifiers that can be used in COPY_BB in COPY_CFUN. COPY_BB should be
+ a freshly-made copy of SRC_BB. This really just shifts the uids of SRC_BB
+ by NEXT_UID. NEXT_UID should be the same for all the basic blocks of a
+ single function as the uids associate individual gcond stmts with a Boolean
+ expression, so we cannot reassign or create new uids randomly. We need to
+ shift (rebase) the uids as the inlined function can use the same range of
+ identifiers as the caller.
+
+ This is used for condition coverage (MC/DC). */
+static void
+rebase_condition_uids (struct function *src_cfun, basic_block src_bb,
+ struct function *dst_cfun, basic_block dst_bb,
+ unsigned next_uid)
+{
+ gimple_stmt_iterator gsi_new, gsi_old;
+ for (gsi_new = gsi_start_bb (dst_bb), gsi_old = gsi_start_bb (src_bb);
+ !gsi_end_p (gsi_new) && !gsi_end_p (gsi_old);
+ gsi_next (&gsi_new), gsi_next (&gsi_old))
+ {
+ gimple *stmt_new = *gsi_new;
+ gimple *stmt_old = *gsi_old;
+ if (src_cfun->cond_uids && is_a <gcond*> (stmt_new))
+ {
+ gcond *cond_new = as_a <gcond*> (stmt_new);
+ gcond *cond_old = as_a <gcond*> (stmt_old);
+ unsigned *v = src_cfun->cond_uids->get (cond_old);
+ if (v)
+ gimple_associate_condition_with_expr (dst_cfun, cond_new,
+ *v + next_uid);
+ }
+ }
+}
+
/* Make a copy of the body of FN so that it can be inserted inline in
another function. Walks FN via CFG, returns new fndecl. */
@@ -3142,6 +3180,12 @@ copy_cfg_body (copy_body_data * id,
id->eh_map = duplicate_eh_regions (cfun_to_copy, NULL, id->eh_lp_nr,
remap_decl_1, id);
+ /* Find the conditional uid base for the inlined function, but only when
+ there are conds to inline. */
+ unsigned next_uid_base = 0;
+ if (src_cfun->cond_uids)
+ next_uid_base = next_free_cond_uid (cfun);
+
/* Use aux pointers to map the original blocks to copy. */
FOR_EACH_BB_FN (bb, cfun_to_copy)
if (!id->blocks_to_copy || bitmap_bit_p (id->blocks_to_copy, bb->index))
@@ -3150,6 +3194,7 @@ copy_cfg_body (copy_body_data * id,
bb->aux = new_bb;
new_bb->aux = bb;
new_bb->loop_father = entry_block_map->loop_father;
+ rebase_condition_uids (src_cfun, bb, cfun, new_bb, next_uid_base);
}
last = last_basic_block_for_fn (cfun);
--
2.47.3