diff mbox series

combine: Fix ICE with substitution of CONST_INT into PRE_DEC argument [PR104446]

Message ID 20220210095503.GY2646553@tucnak
State New
Headers show
Series combine: Fix ICE with substitution of CONST_INT into PRE_DEC argument [PR104446] | expand

Commit Message

Jakub Jelinek Feb. 10, 2022, 9:55 a.m. UTC
Hi!

The following testcase ICEs, because combine substitutes
(insn 10 9 11 2 (set (reg/v:SI 7 sp [ a ])
        (const_int 0 [0])) "pr104446.c":9:5 81 {*movsi_internal}
     (nil))
(insn 13 11 14 2 (set (mem/f:SI (pre_dec:SI (reg/f:SI 7 sp)) [0  S4 A32])
        (reg:SI 85)) "pr104446.c":10:3 56 {*pushsi2}
     (expr_list:REG_DEAD (reg:SI 85)
        (expr_list:REG_ARGS_SIZE (const_int 16 [0x10])
            (nil))))
forming
(insn 13 11 14 2 (set (mem/f:SI (pre_dec:SI (const_int 0 [0])) [0  S4 A32])
        (reg:SI 85)) "pr104446.c":10:3 56 {*pushsi2}
     (expr_list:REG_DEAD (reg:SI 85)
        (expr_list:REG_ARGS_SIZE (const_int 16 [0x10])
            (nil))))
which is invalid RTL (pre_dec's argument must be a REG).
I know substitution creates various forms of invalid RTL and hopes that
invalid RTL just won't recog.
But unfortunately in this case we ICE before we get to recog, as
try_combine does:
  if (n_auto_inc)
    {
      int new_n_auto_inc = 0;
      for_each_inc_dec (newpat, count_auto_inc, &new_n_auto_inc);

      if (n_auto_inc != new_n_auto_inc)
        {
          if (dump_file && (dump_flags & TDF_DETAILS))
            fprintf (dump_file, "Number of auto_inc expressions changed\n");
          undo_all ();
          return 0;
        }
    }
and for_each_inc_dec under the hood will do e.g. for the PRE_DEC case:
    case PRE_DEC:
    case POST_DEC:
      {
        poly_int64 size = GET_MODE_SIZE (GET_MODE (mem));
        rtx r1 = XEXP (x, 0);
        rtx c = gen_int_mode (-size, GET_MODE (r1));
        return fn (mem, x, r1, r1, c, data);
      }
and that code rightfully expects that the PRE_DEC operand has non-VOIDmode
(as it needs to be a REG) - gen_int_mode for VOIDmode results in ICE.
I think it is better not to emit the clearly invalid RTL during substitution
like we do for other cases, than to adding workarounds for invalid IL
created by combine to rtlanal.cc and perhaps elsewhere.
As for the testcase, of course it is UB at runtime to modify sp that way,
but if such code is never reached, we must compile it, not to ICE on it.
And I don't see why on other targets which use the autoinc rtxes much more
it couldn't happen with other registers.

Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?

2022-02-10  Jakub Jelinek  <jakub@redhat.com>

	PR middle-end/104446
	* combine.cc (subst): Don't substitute CONST_INTs into RTX_AUTOINC
	operands.

	* gcc.target/i386/pr104446.c: New test.


	Jakub

Comments

Segher Boessenkool Feb. 10, 2022, 4:10 p.m. UTC | #1
Hi!

On Thu, Feb 10, 2022 at 10:55:03AM +0100, Jakub Jelinek wrote:
> The following testcase ICEs, because combine substitutes
> (insn 10 9 11 2 (set (reg/v:SI 7 sp [ a ])
>         (const_int 0 [0])) "pr104446.c":9:5 81 {*movsi_internal}
>      (nil))
> (insn 13 11 14 2 (set (mem/f:SI (pre_dec:SI (reg/f:SI 7 sp)) [0  S4 A32])
>         (reg:SI 85)) "pr104446.c":10:3 56 {*pushsi2}
>      (expr_list:REG_DEAD (reg:SI 85)
>         (expr_list:REG_ARGS_SIZE (const_int 16 [0x10])
>             (nil))))
> forming
> (insn 13 11 14 2 (set (mem/f:SI (pre_dec:SI (const_int 0 [0])) [0  S4 A32])
>         (reg:SI 85)) "pr104446.c":10:3 56 {*pushsi2}
>      (expr_list:REG_DEAD (reg:SI 85)
>         (expr_list:REG_ARGS_SIZE (const_int 16 [0x10])
>             (nil))))
> which is invalid RTL (pre_dec's argument must be a REG).
> I know substitution creates various forms of invalid RTL and hopes that
> invalid RTL just won't recog.

This is not a "hope"; it is a requirement.  If the backend accepts
invalid insns, this is a bug in the backend.

> But unfortunately in this case we ICE before we get to recog, as
> try_combine does:
>   if (n_auto_inc)
>     {
>       int new_n_auto_inc = 0;
>       for_each_inc_dec (newpat, count_auto_inc, &new_n_auto_inc);
> 
>       if (n_auto_inc != new_n_auto_inc)
>         {
>           if (dump_file && (dump_flags & TDF_DETAILS))
>             fprintf (dump_file, "Number of auto_inc expressions changed\n");
>           undo_all ();
>           return 0;
>         }
>     }
> and for_each_inc_dec under the hood will do e.g. for the PRE_DEC case:
>     case PRE_DEC:
>     case POST_DEC:
>       {
>         poly_int64 size = GET_MODE_SIZE (GET_MODE (mem));
>         rtx r1 = XEXP (x, 0);
>         rtx c = gen_int_mode (-size, GET_MODE (r1));
>         return fn (mem, x, r1, r1, c, data);
>       }
> and that code rightfully expects that the PRE_DEC operand has non-VOIDmode
> (as it needs to be a REG) - gen_int_mode for VOIDmode results in ICE.
> I think it is better not to emit the clearly invalid RTL during substitution
> like we do for other cases, than to adding workarounds for invalid IL
> created by combine to rtlanal.cc and perhaps elsewhere.

But we do have that in other cases, and not just for combine.  IMO it
is a good idea to robustify for_each_inc_dec (simply have it skip if the
address is not MODE_INT or such).  It also is a good idea to robustify
combine subst, just as you do.  It is best to do both!

> As for the testcase, of course it is UB at runtime to modify sp that way,
> but if such code is never reached, we must compile it, not to ICE on it.

It is an error at compile time already.  The stack pointer is a fixed
register.  The generic parts of the compiler use it, it is not just a
backend thing.

There are many more ways to ICE the compiler with register vars, btw.

And yes, ICEs are bad of course :-)  QoI thing...

> And I don't see why on other targets which use the autoinc rtxes much more
> it couldn't happen with other registers.

Yes.  My point (in the PR) was that it is easy enough to make this valid
code instead!

> 	PR middle-end/104446
> 	* combine.cc (subst): Don't substitute CONST_INTs into RTX_AUTOINC
> 	operands.
> 
> 	* gcc.target/i386/pr104446.c: New test.

> +/* PR middle-end/104446 */
> +/* { dg-do compile { target ia32 } } */
> +/* { dg-options "-O2 -mrtd" } */
> +
> +register volatile int a __asm__("%esp");
> +void foo (void *);
> +void bar (void *);
> +
> +void
> +baz (void)
> +{
> +  foo (__builtin_return_address (0));
> +  a = 0;
> +  bar (__builtin_return_address (0));
> +}

So does it not fail if you make this valid code (by using another
register)?  bp, si, or di maybe?

Okay with that fixed.  If fixing it is too hard, okay like this (I don't
have to maintain other peoples' backends' testsuites after all...)

Thanks!


Segher
Jakub Jelinek Feb. 10, 2022, 4:21 p.m. UTC | #2
On Thu, Feb 10, 2022 at 10:10:13AM -0600, Segher Boessenkool wrote:
> >     case POST_DEC:
> >       {
> >         poly_int64 size = GET_MODE_SIZE (GET_MODE (mem));
> >         rtx r1 = XEXP (x, 0);
> >         rtx c = gen_int_mode (-size, GET_MODE (r1));
> >         return fn (mem, x, r1, r1, c, data);
> >       }
> > and that code rightfully expects that the PRE_DEC operand has non-VOIDmode
> > (as it needs to be a REG) - gen_int_mode for VOIDmode results in ICE.
> > I think it is better not to emit the clearly invalid RTL during substitution
> > like we do for other cases, than to adding workarounds for invalid IL
> > created by combine to rtlanal.cc and perhaps elsewhere.
> 
> But we do have that in other cases, and not just for combine.  IMO it
> is a good idea to robustify for_each_inc_dec (simply have it skip if the
> address is not MODE_INT or such).  It also is a good idea to robustify
> combine subst, just as you do.  It is best to do both!

Well, skipping would mean the callback isn't called on it so the autoinc
isn't detected.
But we could do:
         rtx c = (GET_MODE (r1) == VOIDmode
		  ? GEN_INT (-size) : gen_int_mode (-size, GET_MODE (r1)));
with a comment why do do that.

> > 	PR middle-end/104446
> > 	* combine.cc (subst): Don't substitute CONST_INTs into RTX_AUTOINC
> > 	operands.
> > 
> > 	* gcc.target/i386/pr104446.c: New test.
> 
> > +/* PR middle-end/104446 */
> > +/* { dg-do compile { target ia32 } } */
> > +/* { dg-options "-O2 -mrtd" } */
> > +
> > +register volatile int a __asm__("%esp");
> > +void foo (void *);
> > +void bar (void *);
> > +
> > +void
> > +baz (void)
> > +{
> > +  foo (__builtin_return_address (0));
> > +  a = 0;
> > +  bar (__builtin_return_address (0));
> > +}
> 
> So does it not fail if you make this valid code (by using another
> register)?  bp, si, or di maybe?

Not on x86, that isn't a general auto-inc-dec target, but uses PRE_DEC
etc. only for the sp hard register.
For other targets we'd need to somehow convince all the earlier passes
(gimple and RTL) not to try to propagate the constant value into the
addition inside of a memory address.

	Jakub
Segher Boessenkool Feb. 10, 2022, 4:42 p.m. UTC | #3
On Thu, Feb 10, 2022 at 05:21:07PM +0100, Jakub Jelinek wrote:
> On Thu, Feb 10, 2022 at 10:10:13AM -0600, Segher Boessenkool wrote:
> > But we do have that in other cases, and not just for combine.  IMO it
> > is a good idea to robustify for_each_inc_dec (simply have it skip if the
> > address is not MODE_INT or such).  It also is a good idea to robustify
> > combine subst, just as you do.  It is best to do both!
> 
> Well, skipping would mean the callback isn't called on it so the autoinc
> isn't detected.

Which is fine, because it isn't valid in the first place!  The only
thing we have to do is not ICE, this RTL is not long for this world.

> > So does it not fail if you make this valid code (by using another
> > register)?  bp, si, or di maybe?
> 
> Not on x86, that isn't a general auto-inc-dec target, but uses PRE_DEC
> etc. only for the sp hard register.

Ugh.  Does it have any benefit from using autoinc at all then?  (Actual
benefit, not notational convenience).

> For other targets we'd need to somehow convince all the earlier passes
> (gimple and RTL) not to try to propagate the constant value into the
> addition inside of a memory address.

I wonder if there is any target for which autoinc is more convenient
than inconvenient (other than in inline asm, a whole separate
challenge) :-(


Segher
Jakub Jelinek Feb. 10, 2022, 5:23 p.m. UTC | #4
On Thu, Feb 10, 2022 at 10:42:03AM -0600, Segher Boessenkool wrote:
> > Not on x86, that isn't a general auto-inc-dec target, but uses PRE_DEC
> > etc. only for the sp hard register.
> 
> Ugh.  Does it have any benefit from using autoinc at all then?  (Actual
> benefit, not notational convenience).

That is the most accurate description of what the push/pop instructions
actually do, and the backend has been doing it for decades.

	Jakub
Segher Boessenkool Feb. 10, 2022, 8:04 p.m. UTC | #5
On Thu, Feb 10, 2022 at 06:23:58PM +0100, Jakub Jelinek wrote:
> On Thu, Feb 10, 2022 at 10:42:03AM -0600, Segher Boessenkool wrote:
> > > Not on x86, that isn't a general auto-inc-dec target, but uses PRE_DEC
> > > etc. only for the sp hard register.
> > 
> > Ugh.  Does it have any benefit from using autoinc at all then?  (Actual
> > benefit, not notational convenience).
> 
> That is the most accurate description of what the push/pop instructions
> actually do, and the backend has been doing it for decades.

It is exactly as accurate as the simpler direct representation as a
parallel (which we have used since 1992).


Segher
diff mbox series

Patch

--- gcc/combine.cc.jj	2022-02-08 20:08:13.821404850 +0100
+++ gcc/combine.cc	2022-02-09 12:19:56.768294280 +0100
@@ -5534,6 +5534,12 @@  subst (rtx x, rtx from, rtx to, int in_d
 		  if (!x)
 		    return gen_rtx_CLOBBER (VOIDmode, const0_rtx);
 		}
+	      /* CONST_INTs shouldn't be substituted into PRE_DEC, PRE_MODIFY
+		 etc. arguments, otherwise we can ICE before trying to recog
+		 it.  See PR104446.  */
+	      else if (CONST_SCALAR_INT_P (new_rtx)
+		       && GET_RTX_CLASS (GET_CODE (x)) == RTX_AUTOINC)
+		return gen_rtx_CLOBBER (VOIDmode, const0_rtx);
 	      else
 		SUBST (XEXP (x, i), new_rtx);
 	    }
--- gcc/testsuite/gcc.target/i386/pr104446.c.jj	2022-02-09 12:29:14.311505584 +0100
+++ gcc/testsuite/gcc.target/i386/pr104446.c	2022-02-09 12:28:35.329050754 +0100
@@ -0,0 +1,15 @@ 
+/* PR middle-end/104446 */
+/* { dg-do compile { target ia32 } } */
+/* { dg-options "-O2 -mrtd" } */
+
+register volatile int a __asm__("%esp");
+void foo (void *);
+void bar (void *);
+
+void
+baz (void)
+{
+  foo (__builtin_return_address (0));
+  a = 0;
+  bar (__builtin_return_address (0));
+}