diff mbox series

[committed] arm: correctly handle inequality comparisons against max constants [PR100563]

Message ID 20210513104819.1081411-1-rearnsha@arm.com
State New
Headers show
Series [committed] arm: correctly handle inequality comparisons against max constants [PR100563] | expand

Commit Message

Richard Earnshaw May 13, 2021, 10:48 a.m. UTC
Normally we expect the gimple optimizers to fold away comparisons that
are always true, but at some lower optimization levels this is not
always the case, so the back-end has to be able to generate correct
code in these cases.

In this example, we have a comparison of the form

  (unsigned long long) op <= ~0ULL

which, of course is always true.

Normally, in the arm back-end we handle these expansions where the
immediate cannot be handled directly by adding 1 to the constant and
then adjusting the comparison operator:

  (unsigned long long) op < CONST + 1

but we cannot do that when the constant is already the largest value.

Fortunately, we observe that the comparisons we need to handle this
way are either always true or always false, so instead of forming a
comparison against the maximum value, we can replace it with a
comparison against the minimum value (which just happens to also be a
constant we can handle.  So

  op1 <= ~0ULL -> op1 >= 0U
  op1 > ~0ULL -> op1 < 0U

  op1 <= LONG_LONG_INT_MAX -> op1 >= (-LONG_LONG_INT_MAX - 1)
  op1 > LONG_LONG_INT_MAX -> op1 < (-LONG_LONG_INT_MAX - 1)

gcc:
	PR target/100563
	* config/arm/arm.c (arm_canonicalize_comparison): Correctly
	canonicalize DImode inequality comparisons against the
	maximum integral value.

gcc/testsuite:
	* gcc.dg/pr100563.c: New test.
---
 gcc/config/arm/arm.c            | 29 +++++++++++++++++++++++++----
 gcc/testsuite/gcc.dg/pr100563.c |  9 +++++++++
 2 files changed, 34 insertions(+), 4 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/pr100563.c

Comments

Hans-Peter Nilsson May 17, 2021, 8:52 p.m. UTC | #1
On Thu, 13 May 2021, Richard Earnshaw via Gcc-patches wrote:
>
> Normally we expect the gimple optimizers to fold away comparisons that
> are always true, but at some lower optimization levels this is not
> always the case, so the back-end has to be able to generate correct
> code in these cases.
>
> In this example, we have a comparison of the form
>
>   (unsigned long long) op <= ~0ULL
>
> which, of course is always true.
>
> Normally, in the arm back-end we handle these expansions where the
> immediate cannot be handled directly by adding 1 to the constant and
> then adjusting the comparison operator:
>
>   (unsigned long long) op < CONST + 1
>
> but we cannot do that when the constant is already the largest value.

Sounds like a target-independent bug in the making, lurking and
waiting for a target to do the above adjustment but missing the
bounds-check.

>  gcc/testsuite/gcc.dg/pr100563.c |  9 +++++++++
>  2 files changed, 34 insertions(+), 4 deletions(-)
>  create mode 100644 gcc/testsuite/gcc.dg/pr100563.c

I'll therefore humbly suggest the test-case adjusted to be a
run-time check (and if not done by others, projecting to do that
myself...some time late next summer9.

brgds, H-P
Richard Earnshaw May 18, 2021, 10:26 a.m. UTC | #2
On 17/05/2021 21:52, Hans-Peter Nilsson wrote:
> On Thu, 13 May 2021, Richard Earnshaw via Gcc-patches wrote:
>>
>> Normally we expect the gimple optimizers to fold away comparisons that
>> are always true, but at some lower optimization levels this is not
>> always the case, so the back-end has to be able to generate correct
>> code in these cases.
>>
>> In this example, we have a comparison of the form
>>
>>    (unsigned long long) op <= ~0ULL
>>
>> which, of course is always true.
>>
>> Normally, in the arm back-end we handle these expansions where the
>> immediate cannot be handled directly by adding 1 to the constant and
>> then adjusting the comparison operator:
>>
>>    (unsigned long long) op < CONST + 1
>>
>> but we cannot do that when the constant is already the largest value.
> 
> Sounds like a target-independent bug in the making, lurking and
> waiting for a target to do the above adjustment but missing the
> bounds-check.
> 

The normal canonicalize_comparison operation tries to simplify 
expressions by reducing the value towards zero and making the 
appropriate adjustment to the comparison.  Arm is a bit different due to 
the way constants are encoded and also because of the limitations on how 
we expand DImode in the backend.  So I don't think this is quite as 
general as you suggest.

>>   gcc/testsuite/gcc.dg/pr100563.c |  9 +++++++++
>>   2 files changed, 34 insertions(+), 4 deletions(-)
>>   create mode 100644 gcc/testsuite/gcc.dg/pr100563.c
> 
> I'll therefore humbly suggest the test-case adjusted to be a
> run-time check (and if not done by others, projecting to do that
> myself...some time late next summer9.

That being said, I won't object if you want to /add/ an additional test 
to try to catch this; but this test had to be quite carefully crafted to 
hit the specific bug in the arm expanders, so I'd prefer that it wasn't 
modified directly.

R.

> 
> brgds, H-P
>
Hans-Peter Nilsson May 18, 2021, 10:57 a.m. UTC | #3
On Tue, 18 May 2021, Richard Earnshaw wrote:
> On 17/05/2021 21:52, Hans-Peter Nilsson wrote:
> > On Thu, 13 May 2021, Richard Earnshaw via Gcc-patches wrote:
> > >
> > > Normally we expect the gimple optimizers to fold away comparisons that
> > > are always true, but at some lower optimization levels this is not
> > > always the case, so the back-end has to be able to generate correct
> > > code in these cases.
> > >
> > > In this example, we have a comparison of the form
> > >
> > >    (unsigned long long) op <= ~0ULL
> > >
> > > which, of course is always true.
> > >
> > > Normally, in the arm back-end we handle these expansions where the
> > > immediate cannot be handled directly by adding 1 to the constant and
> > > then adjusting the comparison operator:
> > >
> > >    (unsigned long long) op < CONST + 1
> > >
> > > but we cannot do that when the constant is already the largest value.
> >
> > Sounds like a target-independent bug in the making, lurking and
> > waiting for a target to do the above adjustment but missing the
> > bounds-check.
>
> The normal canonicalize_comparison operation tries to simplify expressions by
> reducing the value towards zero and making the appropriate adjustment to the
> comparison.  Arm is a bit different due to the way constants are encoded and
> also because of the limitations on how we expand DImode in the backend.  So I
> don't think this is quite as general as you suggest.

It just seemed I had done something similar somewhere in another
port, though I can't find it.  I could also have only read it...

> > >   gcc/testsuite/gcc.dg/pr100563.c |  9 +++++++++
> > >   2 files changed, 34 insertions(+), 4 deletions(-)
> > >   create mode 100644 gcc/testsuite/gcc.dg/pr100563.c
> >
> > I'll therefore humbly suggest the test-case adjusted to be a
> > run-time check (and if not done by others, projecting to do that
> > myself...some time late next summer9.
>
> That being said, I won't object if you want to /add/ an additional test to try
> to catch this; but this test had to be quite carefully crafted to hit the
> specific bug in the arm expanders, so I'd prefer that it wasn't modified
> directly.

Right.  I expressed myself poorly as I share your concerns on
this point: after the public commit of a test that had no error,
it must never be modified.  To wit, such an "adjustment" must be
made in the form of an additional, separate test.

brgds, H-P
Richard Biener May 18, 2021, 11:41 a.m. UTC | #4
On Tue, May 18, 2021 at 1:26 PM Richard Earnshaw via Gcc-patches
<gcc-patches@gcc.gnu.org> wrote:
>
>
>
> On 17/05/2021 21:52, Hans-Peter Nilsson wrote:
> > On Thu, 13 May 2021, Richard Earnshaw via Gcc-patches wrote:
> >>
> >> Normally we expect the gimple optimizers to fold away comparisons that
> >> are always true, but at some lower optimization levels this is not
> >> always the case, so the back-end has to be able to generate correct
> >> code in these cases.
> >>
> >> In this example, we have a comparison of the form
> >>
> >>    (unsigned long long) op <= ~0ULL
> >>
> >> which, of course is always true.
> >>
> >> Normally, in the arm back-end we handle these expansions where the
> >> immediate cannot be handled directly by adding 1 to the constant and
> >> then adjusting the comparison operator:
> >>
> >>    (unsigned long long) op < CONST + 1
> >>
> >> but we cannot do that when the constant is already the largest value.
> >
> > Sounds like a target-independent bug in the making, lurking and
> > waiting for a target to do the above adjustment but missing the
> > bounds-check.
> >
>
> The normal canonicalize_comparison operation tries to simplify
> expressions by reducing the value towards zero and making the
> appropriate adjustment to the comparison.  Arm is a bit different due to
> the way constants are encoded and also because of the limitations on how
> we expand DImode in the backend.  So I don't think this is quite as
> general as you suggest.
>
> >>   gcc/testsuite/gcc.dg/pr100563.c |  9 +++++++++
> >>   2 files changed, 34 insertions(+), 4 deletions(-)
> >>   create mode 100644 gcc/testsuite/gcc.dg/pr100563.c
> >
> > I'll therefore humbly suggest the test-case adjusted to be a
> > run-time check (and if not done by others, projecting to do that
> > myself...some time late next summer9.
>
> That being said, I won't object if you want to /add/ an additional test
> to try to catch this; but this test had to be quite carefully crafted to
> hit the specific bug in the arm expanders, so I'd prefer that it wasn't
> modified directly.

Note some less special crafting might be needed when using
the GIMPLE FE and you start right with RTL expansion
via __GIMPLE (ssa,startwith("expand"))
-fdump-tree-optimized-gimple should provide rough GIMPLE FE
input.

Richard.

> R.
>
> >
> > brgds, H-P
> >
diff mbox series

Patch

diff --git a/gcc/config/arm/arm.c b/gcc/config/arm/arm.c
index 2962071adfd..d0c0c50be97 100644
--- a/gcc/config/arm/arm.c
+++ b/gcc/config/arm/arm.c
@@ -5563,9 +5563,20 @@  arm_canonicalize_comparison (int *code, rtx *op0, rtx *op1,
 			return;
 		      *op1 = GEN_INT (i + 1);
 		      *code = *code == GT ? GE : LT;
-		      return;
 		    }
-		  break;
+		  else
+		    {
+		      /* GT maxval is always false, LE maxval is always true.
+			 We can't fold that away here as we must make a
+			 comparison, but we can fold them to comparisons
+			 with the same result that can be handled:
+			   op0 GT maxval -> op0 LT minval
+			   op0 LE maxval -> op0 GE minval
+			 where minval = (-maxval - 1).  */
+		      *op1 = GEN_INT (-maxval - 1);
+		      *code = *code == GT ? LT : GE;
+		    }
+		  return;
 
 		case GTU:
 		case LEU:
@@ -5578,9 +5589,19 @@  arm_canonicalize_comparison (int *code, rtx *op0, rtx *op1,
 			return;
 		      *op1 = GEN_INT (i + 1);
 		      *code = *code == GTU ? GEU : LTU;
-		      return;
 		    }
-		  break;
+		  else
+		    {
+		      /* GTU ~0 is always false, LEU ~0 is always true.
+			 We can't fold that away here as we must make a
+			 comparison, but we can fold them to comparisons
+			 with the same result that can be handled:
+			   op0 GTU ~0 -> op0 LTU 0
+			   op0 LEU ~0 -> op0 GEU 0.  */
+		      *op1 = const0_rtx;
+		      *code = *code == GTU ? LTU : GEU;
+		    }
+		  return;
 
 		default:
 		  gcc_unreachable ();
diff --git a/gcc/testsuite/gcc.dg/pr100563.c b/gcc/testsuite/gcc.dg/pr100563.c
new file mode 100644
index 00000000000..812eb9e6ae2
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/pr100563.c
@@ -0,0 +1,9 @@ 
+/* { dg-do compile } */
+/* { dg-options "-Og" } */
+unsigned long long e(void);
+void f(int);
+void a() {
+  short b = -1, c = (int)&b;
+  unsigned long long d = e();
+  f(b >= d);
+}