Fix -fcompare-debug issues in find_many_sub_basic_blocks (PR target/81325)

Message ID 20170913104417.GW1701@tucnak
State New
Headers show
Series
  • Fix -fcompare-debug issues in find_many_sub_basic_blocks (PR target/81325)
Related show

Commit Message

Jakub Jelinek Sept. 13, 2017, 10:44 a.m.
Hi!

The following testcase fails on powerpc64le with -fcompare-debug.
The problem is that find_many_sub_basic_blocks is called during
expansion on a basic block where originally two __atomic_fetch_add
calls have some debug stmts in between.  The rs6000 backend expands
those atomic calls into a loop that starts with a CODE_LABEL and
ends with a JUMP_INSN, so find_many_sub_basic_blocks must
end a basic block after the JUMP_INSN from the first call and
start a new basic block at the start of the second call.
If there are no DEBUG_INSNs in between, it creates just those 2
basic blocks, which is correct, but it doesn't ignore DEBUG_INSNs when
deciding what to do and thus when there are DEBUG_ISNSs in between
the flow_transfer_insn (JUMP_INSN) and CODE_LABEL, it creates yet
another basic block that has just the DEBUG_INSNs in it.  That means
different cfg between -g and -g0 and more differences later on.

As I wrote in the PR, if we have 2 loops and some code in between
at GIMPLE, and in that code in between DCE all the non-debug stmts and
have only debug stmts in there, we consider it a forwarder block and delete
it with all those debug stmts (it is one of the unfortunate cases where we
drop them on the floor, but there isn't really much we can do with those
without adding new extensions like conditional debug stmts or debug phis).

The following patch attempts to do the same thing on RTL during
find_many_sub_basic_blocks, i.e. ignore debug stmts when deciding what to
do, trying to preserve debug insns somewhere if that is easily possible
(if there is say a conditional jump with a fallthrough into debug insns
followed by some real insn (not a CODE_LABEL nor BARRIER), the debug
insns can go at the start of the fallthrough bb that would be created
anyway).  If we have an insn that must end a bb and another one that must
start a bb (CODE_LABEL) and only debug insns in between, we remove them.

Bootstrapped/regtested on x86_64-linux and i686-linux (in these two cases
I've also gathered statistics and no debug insns during the
bootstraps/regtests were actually removed) and powerpc64le-linux (forgot to
apply the statistics patch, so no details, but it surely triggers at least
on the testcase).  Ok for trunk?

2017-09-13  Jakub Jelinek  <jakub@redhat.com>

	PR target/81325
	* cfgbuild.c (find_bb_boundaries): Ignore debug insns in decisions
	if and where to split a bb, except for splitting before debug insn
	sequences followed by non-label real insn.  Delete debug insns
	in between basic blocks.

	* g++.dg/cpp0x/pr81325.C: New test.


	Jakub

Comments

Richard Biener Sept. 13, 2017, 11:09 a.m. | #1
On Wed, 13 Sep 2017, Jakub Jelinek wrote:

> Hi!
> 
> The following testcase fails on powerpc64le with -fcompare-debug.
> The problem is that find_many_sub_basic_blocks is called during
> expansion on a basic block where originally two __atomic_fetch_add
> calls have some debug stmts in between.  The rs6000 backend expands
> those atomic calls into a loop that starts with a CODE_LABEL and
> ends with a JUMP_INSN, so find_many_sub_basic_blocks must
> end a basic block after the JUMP_INSN from the first call and
> start a new basic block at the start of the second call.
> If there are no DEBUG_INSNs in between, it creates just those 2
> basic blocks, which is correct, but it doesn't ignore DEBUG_INSNs when
> deciding what to do and thus when there are DEBUG_ISNSs in between
> the flow_transfer_insn (JUMP_INSN) and CODE_LABEL, it creates yet
> another basic block that has just the DEBUG_INSNs in it.  That means
> different cfg between -g and -g0 and more differences later on.
> 
> As I wrote in the PR, if we have 2 loops and some code in between
> at GIMPLE, and in that code in between DCE all the non-debug stmts and
> have only debug stmts in there, we consider it a forwarder block and delete
> it with all those debug stmts (it is one of the unfortunate cases where we
> drop them on the floor, but there isn't really much we can do with those
> without adding new extensions like conditional debug stmts or debug phis).
> 
> The following patch attempts to do the same thing on RTL during
> find_many_sub_basic_blocks, i.e. ignore debug stmts when deciding what to
> do, trying to preserve debug insns somewhere if that is easily possible
> (if there is say a conditional jump with a fallthrough into debug insns
> followed by some real insn (not a CODE_LABEL nor BARRIER), the debug
> insns can go at the start of the fallthrough bb that would be created
> anyway).  If we have an insn that must end a bb and another one that must
> start a bb (CODE_LABEL) and only debug insns in between, we remove them.
> 
> Bootstrapped/regtested on x86_64-linux and i686-linux (in these two cases
> I've also gathered statistics and no debug insns during the
> bootstraps/regtests were actually removed) and powerpc64le-linux (forgot to
> apply the statistics patch, so no details, but it surely triggers at least
> on the testcase).  Ok for trunk?

Ok.

Thanks,
Richard.

> 2017-09-13  Jakub Jelinek  <jakub@redhat.com>
> 
> 	PR target/81325
> 	* cfgbuild.c (find_bb_boundaries): Ignore debug insns in decisions
> 	if and where to split a bb, except for splitting before debug insn
> 	sequences followed by non-label real insn.  Delete debug insns
> 	in between basic blocks.
> 
> 	* g++.dg/cpp0x/pr81325.C: New test.
> 
> --- gcc/cfgbuild.c.jj	2017-09-12 21:57:53.084514168 +0200
> +++ gcc/cfgbuild.c	2017-09-13 09:15:57.701521179 +0200
> @@ -442,9 +442,10 @@ find_bb_boundaries (basic_block bb)
>    rtx_insn *end = BB_END (bb), *x;
>    rtx_jump_table_data *table;
>    rtx_insn *flow_transfer_insn = NULL;
> +  rtx_insn *debug_insn = NULL;
>    edge fallthru = NULL;
>  
> -  if (insn == BB_END (bb))
> +  if (insn == end)
>      return;
>  
>    if (LABEL_P (insn))
> @@ -455,22 +456,43 @@ find_bb_boundaries (basic_block bb)
>      {
>        enum rtx_code code = GET_CODE (insn);
>  
> +      if (code == DEBUG_INSN)
> +	{
> +	  if (flow_transfer_insn && !debug_insn)
> +	    debug_insn = insn;
> +	}
>        /* In case we've previously seen an insn that effects a control
>  	 flow transfer, split the block.  */
> -      if ((flow_transfer_insn || code == CODE_LABEL)
> -	  && inside_basic_block_p (insn))
> +      else if ((flow_transfer_insn || code == CODE_LABEL)
> +	       && inside_basic_block_p (insn))
>  	{
> -	  fallthru = split_block (bb, PREV_INSN (insn));
> +	  rtx_insn *prev = PREV_INSN (insn);
> +
> +	  /* If the first non-debug inside_basic_block_p insn after a control
> +	     flow transfer is not a label, split the block before the debug
> +	     insn instead of before the non-debug insn, so that the debug
> +	     insns are not lost.  */
> +	  if (debug_insn && code != CODE_LABEL && code != BARRIER)
> +	    prev = PREV_INSN (debug_insn);
> +	  fallthru = split_block (bb, prev);
>  	  if (flow_transfer_insn)
>  	    {
>  	      BB_END (bb) = flow_transfer_insn;
>  
> +	      rtx_insn *next;
>  	      /* Clean up the bb field for the insns between the blocks.  */
>  	      for (x = NEXT_INSN (flow_transfer_insn);
>  		   x != BB_HEAD (fallthru->dest);
> -		   x = NEXT_INSN (x))
> -		if (!BARRIER_P (x))
> -		  set_block_for_insn (x, NULL);
> +		   x = next)
> +		{
> +		  next = NEXT_INSN (x);
> +		  /* Debug insns should not be in between basic blocks,
> +		     drop them on the floor.  */
> +		  if (DEBUG_INSN_P (x))
> +		    delete_insn (x);
> +		  else if (!BARRIER_P (x))
> +		    set_block_for_insn (x, NULL);
> +		}
>  	    }
>  
>  	  bb = fallthru->dest;
> @@ -480,6 +502,7 @@ find_bb_boundaries (basic_block bb)
>  	  bb->frequency = 0;
>  	  bb->count = profile_count::uninitialized ();
>  	  flow_transfer_insn = NULL;
> +	  debug_insn = NULL;
>  	  if (code == CODE_LABEL && LABEL_ALT_ENTRY_P (insn))
>  	    make_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun), bb, 0);
>  	}
> @@ -502,17 +525,23 @@ find_bb_boundaries (basic_block bb)
>    /* In case expander replaced normal insn by sequence terminating by
>       return and barrier, or possibly other sequence not behaving like
>       ordinary jump, we need to take care and move basic block boundary.  */
> -  if (flow_transfer_insn)
> +  if (flow_transfer_insn && flow_transfer_insn != end)
>      {
>        BB_END (bb) = flow_transfer_insn;
>  
>        /* Clean up the bb field for the insns that do not belong to BB.  */
> -      x = flow_transfer_insn;
> -      while (x != end)
> +      rtx_insn *next;
> +      for (x = NEXT_INSN (flow_transfer_insn); ; x = next)
>  	{
> -	  x = NEXT_INSN (x);
> -	  if (!BARRIER_P (x))
> +	  next = NEXT_INSN (x);
> +	  /* Debug insns should not be in between basic blocks,
> +	     drop them on the floor.  */
> +	  if (DEBUG_INSN_P (x))
> +	    delete_insn (x);
> +	  else if (!BARRIER_P (x))
>  	    set_block_for_insn (x, NULL);
> +	  if (x == end)
> +	    break;
>  	}
>      }
>  
> --- gcc/testsuite/g++.dg/cpp0x/pr81325.C.jj	2017-09-12 22:17:54.662002067 +0200
> +++ gcc/testsuite/g++.dg/cpp0x/pr81325.C	2017-09-13 09:21:44.536059474 +0200
> @@ -0,0 +1,84 @@
> +// PR target/81325
> +// { dg-do compile { target c++11 } }
> +// { dg-options "-O2 -fcompare-debug" }
> +
> +struct A { A(const char *, const int & = 0); };
> +template <typename> struct B;
> +template <typename> struct C {
> +  int _M_i;
> +  void m_fn1() { __atomic_fetch_add(&_M_i, 0, __ATOMIC_RELAXED); }
> +};
> +struct D {
> +  int *Data;
> +  long Length = 0;
> +  D(int) : Data() {}
> +};
> +template <> struct B<int> : C<int> {};
> +struct F {
> +  B<int> RefCount;
> +  void m_fn2() { RefCount.m_fn1(); }
> +};
> +struct G {
> +  F *Obj;
> +  G(const G &p1) : Obj(p1.Obj) {
> +    if (Obj) {
> +      F *a = 0;
> +      a->m_fn2();
> +    }
> +  }
> +};
> +struct H {
> +  int CPlusPlus : 1;
> +};
> +struct I {
> +  enum {} KindId;
> +};
> +template <typename ResultT, typename ArgT> struct J {
> +  void operator()();
> +  ResultT operator()(ArgT) {}
> +};
> +struct K {
> +  int AllowBind;
> +  I SupportedKind;
> +  I RestrictKind;
> +  G Implementation;
> +};
> +struct L {
> +  L(int) : Implementation(Implementation) {}
> +  K Implementation;
> +};
> +struct M {
> +  int Param1;
> +};
> +struct N {
> +  N(int, L &p2) : Param2(p2) {}
> +  L Param2;
> +};
> +struct O {
> +  L m_fn3();
> +};
> +L ignoringImpCasts(L);
> +J<O, L> b;
> +L hasName(const A &);
> +M hasOverloadedOperatorName(D);
> +J<O, int> c;
> +struct P {
> +  void m_fn4(L, int);
> +};
> +struct Q {
> +  void m_fn5(P *);
> +};
> +H d;
> +void Q::m_fn5(P *p1) {
> +  if (!d.CPlusPlus) {
> +    c();
> +    L e = 0, f = ignoringImpCasts(e);
> +    b(ignoringImpCasts(f)).m_fn3();
> +  }
> +  hasOverloadedOperatorName(0);
> +  hasName("");
> +  L g = 0;
> +  N(0, g);
> +  L h(0);
> +  p1->m_fn4(h, 0);
> +}
> 
> 	Jakub
> 
>

Patch

--- gcc/cfgbuild.c.jj	2017-09-12 21:57:53.084514168 +0200
+++ gcc/cfgbuild.c	2017-09-13 09:15:57.701521179 +0200
@@ -442,9 +442,10 @@  find_bb_boundaries (basic_block bb)
   rtx_insn *end = BB_END (bb), *x;
   rtx_jump_table_data *table;
   rtx_insn *flow_transfer_insn = NULL;
+  rtx_insn *debug_insn = NULL;
   edge fallthru = NULL;
 
-  if (insn == BB_END (bb))
+  if (insn == end)
     return;
 
   if (LABEL_P (insn))
@@ -455,22 +456,43 @@  find_bb_boundaries (basic_block bb)
     {
       enum rtx_code code = GET_CODE (insn);
 
+      if (code == DEBUG_INSN)
+	{
+	  if (flow_transfer_insn && !debug_insn)
+	    debug_insn = insn;
+	}
       /* In case we've previously seen an insn that effects a control
 	 flow transfer, split the block.  */
-      if ((flow_transfer_insn || code == CODE_LABEL)
-	  && inside_basic_block_p (insn))
+      else if ((flow_transfer_insn || code == CODE_LABEL)
+	       && inside_basic_block_p (insn))
 	{
-	  fallthru = split_block (bb, PREV_INSN (insn));
+	  rtx_insn *prev = PREV_INSN (insn);
+
+	  /* If the first non-debug inside_basic_block_p insn after a control
+	     flow transfer is not a label, split the block before the debug
+	     insn instead of before the non-debug insn, so that the debug
+	     insns are not lost.  */
+	  if (debug_insn && code != CODE_LABEL && code != BARRIER)
+	    prev = PREV_INSN (debug_insn);
+	  fallthru = split_block (bb, prev);
 	  if (flow_transfer_insn)
 	    {
 	      BB_END (bb) = flow_transfer_insn;
 
+	      rtx_insn *next;
 	      /* Clean up the bb field for the insns between the blocks.  */
 	      for (x = NEXT_INSN (flow_transfer_insn);
 		   x != BB_HEAD (fallthru->dest);
-		   x = NEXT_INSN (x))
-		if (!BARRIER_P (x))
-		  set_block_for_insn (x, NULL);
+		   x = next)
+		{
+		  next = NEXT_INSN (x);
+		  /* Debug insns should not be in between basic blocks,
+		     drop them on the floor.  */
+		  if (DEBUG_INSN_P (x))
+		    delete_insn (x);
+		  else if (!BARRIER_P (x))
+		    set_block_for_insn (x, NULL);
+		}
 	    }
 
 	  bb = fallthru->dest;
@@ -480,6 +502,7 @@  find_bb_boundaries (basic_block bb)
 	  bb->frequency = 0;
 	  bb->count = profile_count::uninitialized ();
 	  flow_transfer_insn = NULL;
+	  debug_insn = NULL;
 	  if (code == CODE_LABEL && LABEL_ALT_ENTRY_P (insn))
 	    make_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun), bb, 0);
 	}
@@ -502,17 +525,23 @@  find_bb_boundaries (basic_block bb)
   /* In case expander replaced normal insn by sequence terminating by
      return and barrier, or possibly other sequence not behaving like
      ordinary jump, we need to take care and move basic block boundary.  */
-  if (flow_transfer_insn)
+  if (flow_transfer_insn && flow_transfer_insn != end)
     {
       BB_END (bb) = flow_transfer_insn;
 
       /* Clean up the bb field for the insns that do not belong to BB.  */
-      x = flow_transfer_insn;
-      while (x != end)
+      rtx_insn *next;
+      for (x = NEXT_INSN (flow_transfer_insn); ; x = next)
 	{
-	  x = NEXT_INSN (x);
-	  if (!BARRIER_P (x))
+	  next = NEXT_INSN (x);
+	  /* Debug insns should not be in between basic blocks,
+	     drop them on the floor.  */
+	  if (DEBUG_INSN_P (x))
+	    delete_insn (x);
+	  else if (!BARRIER_P (x))
 	    set_block_for_insn (x, NULL);
+	  if (x == end)
+	    break;
 	}
     }
 
--- gcc/testsuite/g++.dg/cpp0x/pr81325.C.jj	2017-09-12 22:17:54.662002067 +0200
+++ gcc/testsuite/g++.dg/cpp0x/pr81325.C	2017-09-13 09:21:44.536059474 +0200
@@ -0,0 +1,84 @@ 
+// PR target/81325
+// { dg-do compile { target c++11 } }
+// { dg-options "-O2 -fcompare-debug" }
+
+struct A { A(const char *, const int & = 0); };
+template <typename> struct B;
+template <typename> struct C {
+  int _M_i;
+  void m_fn1() { __atomic_fetch_add(&_M_i, 0, __ATOMIC_RELAXED); }
+};
+struct D {
+  int *Data;
+  long Length = 0;
+  D(int) : Data() {}
+};
+template <> struct B<int> : C<int> {};
+struct F {
+  B<int> RefCount;
+  void m_fn2() { RefCount.m_fn1(); }
+};
+struct G {
+  F *Obj;
+  G(const G &p1) : Obj(p1.Obj) {
+    if (Obj) {
+      F *a = 0;
+      a->m_fn2();
+    }
+  }
+};
+struct H {
+  int CPlusPlus : 1;
+};
+struct I {
+  enum {} KindId;
+};
+template <typename ResultT, typename ArgT> struct J {
+  void operator()();
+  ResultT operator()(ArgT) {}
+};
+struct K {
+  int AllowBind;
+  I SupportedKind;
+  I RestrictKind;
+  G Implementation;
+};
+struct L {
+  L(int) : Implementation(Implementation) {}
+  K Implementation;
+};
+struct M {
+  int Param1;
+};
+struct N {
+  N(int, L &p2) : Param2(p2) {}
+  L Param2;
+};
+struct O {
+  L m_fn3();
+};
+L ignoringImpCasts(L);
+J<O, L> b;
+L hasName(const A &);
+M hasOverloadedOperatorName(D);
+J<O, int> c;
+struct P {
+  void m_fn4(L, int);
+};
+struct Q {
+  void m_fn5(P *);
+};
+H d;
+void Q::m_fn5(P *p1) {
+  if (!d.CPlusPlus) {
+    c();
+    L e = 0, f = ignoringImpCasts(e);
+    b(ignoringImpCasts(f)).m_fn3();
+  }
+  hasOverloadedOperatorName(0);
+  hasName("");
+  L g = 0;
+  N(0, g);
+  L h(0);
+  p1->m_fn4(h, 0);
+}