diff mbox series

[17/22] Mark contracted-past nodes in reachable

Message ID 20231004123921.634024-18-j@lambda.is
State New
Headers show
Series [01/22] Add condition coverage profiling | expand

Commit Message

Jørgen Kvalsvik Oct. 4, 2023, 12:39 p.m. UTC
When there was no candidate set reduction phase this was not necessary,
as every neighbor's predecessor would always be in the reachable set.
Now that the graph cut is refined multiple times this may not hold,
which would lead to incorrect termination of the ancestors search when a
node in the candidate set was reduced to neighbor (effectively moving
the cut) as its predecessor may have been contracted by.
---
 gcc/testsuite/gcc.misc-tests/gcov-23.c | 75 ++++++++++++++++++++++++++
 gcc/tree-profile.cc                    | 41 +++++++++-----
 2 files changed, 102 insertions(+), 14 deletions(-)
 create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-23.c
diff mbox series

Patch

diff --git a/gcc/testsuite/gcc.misc-tests/gcov-23.c b/gcc/testsuite/gcc.misc-tests/gcov-23.c
new file mode 100644
index 00000000000..c4afc5e700d
--- /dev/null
+++ b/gcc/testsuite/gcc.misc-tests/gcov-23.c
@@ -0,0 +1,75 @@ 
+/* { dg-options "-fprofile-conditions -ftest-coverage -O2 -c" } */
+
+int id (int);
+int idp (int *);
+int err;
+
+/* This becomes problematic only under optimization for the case when the
+   compiler cannot inline the function but have to generate a call.  It is not
+   really interesting to run, only see if it builds.  Notably, both the
+   function calls and the return values are important to construct a
+   problematic graph.
+
+   This test is also a good example of where optimization makes condition
+   coverage unpredictable, but not unusable.  If this is built without
+   optimization the conditions work as you would expect from reading the
+   source.  */
+/* Adapted from cpio-2.14 gnu/utmens.c lutimens ().  */
+int
+mcdc001 (int *v)
+{
+    int adjusted;
+    int adjustment_needed = 0;
+
+    int *ts = v ? &adjusted : 0; /* conditions(0/4) true(0 1) false(0 1) */
+				 /* conditions(end) */
+    if (ts)
+	adjustment_needed = idp (ts);
+    if (adjustment_needed < 0)
+	return -1;
+
+    if (adjustment_needed)  /* conditions(0/2) true(0) false(0) */
+			    /* conditions(end) */
+    {
+	if (adjustment_needed != 3) /* conditions(0/2) true(0) false(0) */
+				    /* conditions(end) */
+	    return -1;
+	if (ts) /* conditions(0/2) true(0) false(0) */
+		/* conditions(end) */
+	    return 0;
+    }
+
+    if (adjustment_needed && idp (&adjusted)) /* conditions(0/2) true(0) false(0) */
+					      /* conditions(end) */
+	return -1;
+    if (adjusted)   /* conditions(0/2) true(0) false(0) */
+		    /* conditions(end) */
+	return idp (ts);
+
+    return -1;
+}
+
+/* This failed when the candidate set internal/contracted-past nodes were not
+   properly marked as reachable in the candidate reduction phase.  */
+/* Adapted from cpio-2.14 gnu/mktime.c mktime_internal ().  */
+int
+mcdc002 ()
+{
+    int a;
+    if (idp (&a)) /* conditions(0/2) true(0) false(0) */
+		  /* conditions(end) */
+    {
+	if (id (a)) /* conditions(0/2) true(0/2) true(0) false(0) */
+		    /* conditions(end) */
+	    goto exit;
+
+	if (err) /* conditions(0/2) true(0/2) true(0) false(0) */
+		 /* conditions(end) */
+	    return -1;
+    }
+
+exit:
+    return a;
+}
+
+/* { dg-final { run-gcov conditions { --conditions gcov-23.c } } } */
diff --git a/gcc/tree-profile.cc b/gcc/tree-profile.cc
index 98593f53862..26e1924d444 100644
--- a/gcc/tree-profile.cc
+++ b/gcc/tree-profile.cc
@@ -279,23 +279,27 @@  single_edge (const vec<edge, va_gc> *edges)
    merged.
 
    Only chains of single-exit single-entry nodes that end with a condition
-   should be contracted.  */
+   should be contracted.  If the optional bitset G is passed, the intermediate
+   "contracted-past" nodes will be recorded, which is only meaningful if the
+   non-source edge is returned.  */
 edge
-contract_edge (edge e)
+contract_edge (edge e, sbitmap G = nullptr)
 {
     edge source = e;
     while (true)
     {
 	basic_block dest = e->dest;
-	if (!single (dest->preds))
-	    return source;
 	if (e->flags & EDGE_DFS_BACK)
 	    return source;
-	if (block_conditional_p (dest))
-	    return e;
 	if (dest->index == EXIT_BLOCK)
 	    return source;
+	if (!single (dest->preds))
+	    return source;
+	if (block_conditional_p (dest))
+	    return e;
 
+	if (G)
+	    bitmap_set_bit (G, dest->index);
 	e = single_edge (dest->succs);
 	if (!e)
 	    return source;
@@ -673,6 +677,8 @@  cond_reachable_from (basic_block p, basic_block q, sbitmap expr,
 	    if (!all_preds_conditional_p (dest, expr))
 		continue;
 
+	    if (dest != e->dest)
+		contract_edge (e, expr);
 	    bitmap_set_bit (expr, dest->index);
 	    out.safe_push (dest);
 	}
@@ -692,8 +698,14 @@  neighborhood (const vec<basic_block>& blocks, sbitmap G, vec<basic_block>& out)
 	    basic_block dest = contract_edge (e)->dest;
 	    if (bitmap_bit_p (G, dest->index))
 		continue;
-	    if (!out.contains (dest))
-		out.safe_push (dest);
+	    if (out.contains (dest))
+		continue;
+	    /* There was a contraction, so replay it but this time also record
+	       the intermediate nodes, so that the reachable set becomes
+	       complete.  This is necessary when reducing the candidate set.  */
+	    if (dest != e->dest)
+		contract_edge (e, G);
+	    out.safe_push (dest);
 	}
     }
 
@@ -784,12 +796,6 @@  isolate_expression (conds_ctx &ctx, basic_block p, vec<basic_block>& out)
 		    bitmap_clear_bit (expr, NG[j]->index);
 		    bitmap_set_bit (expr, b->index);
 		    NG.safe_push (b);
-		    /* It could be that the predecessor edge from the ancestor
-		       was contracted past, in which case we need to mark it to
-		       make sure its ancestors_of do not immediately terminate.  */
-		    for (edge e : b->preds)
-			if (ctx.index_map[e->src->index] > ctx.index_map[p->index])
-			    bitmap_set_bit (reachable, e->src->index);
 		}
 	    }
 	}
@@ -813,6 +819,13 @@  isolate_expression (conds_ctx &ctx, basic_block p, vec<basic_block>& out)
 	bitmap_clear (reachable);
 	for (basic_block b : G)
 	    bitmap_set_bit (reachable, b->index);
+
+	/* Contracted-past nodes in the subgraph must be re-marked, otherwise
+	   the next neighborhood may be computed wrong.  */
+	for (basic_block b : G)
+	    for (edge e : b->succs)
+		if (bitmap_bit_p (reachable, contract_edge (e)->dest->index))
+		    contract_edge (e, reachable);
     }
 
     out.safe_splice (G);