Awesome!  Very many thanks to Richard Sandiford for his inspired
feedback/proof-of-concept that addressed the big endian flaws in
my original patch.  His suggestion to use an RTL get_ref_base_and_offset
is inspired, and significantly cleans up this patch.  Chapeau.

This revision has been tested on x86_64-pc-linux-gnu with make bootstrap
and make -k check, both with and without --target_board=unix{-m32}
with no new failures.  OK for mainline?


2026-07-10  Roger Sayle  <[email protected]>
            Richard Sandiford  <[email protected]>

gcc/ChangeLog
        PR target/48609
        * rtl.cc (rtvec_series_p): Enhance to allow START to be a
        poly_int64 instead of just an int.
        * rtl.h (rtvec_series_p): Update function prototype.
        * rtlanal.cc (vec_series_highpart_p): Now that rtxvec_series_p
        can handle poly_int64, handle modes that aren't constant size.
        (vec_series_lowpart_p): Likewise.
        (get_ref_base_and_offset): New function to determine the base RTX
        and byte offset of an arbitrary expression, typically a SUBREG.
        * rtlanal.h (get_ref_base_and_offset): Prototype here.
        * simplify-rtx.cc (simplify_binary_operation_1) <case VEC_CONCAT>:
        Generalize the existing (vec_concat (first_half) (second_half))
        optimization using the new get_ref_base_and_offset function.

gcc/testsuite/ChangeLog
        PR target/48609
        * gcc.target/i386/pr48609.c: New test case.


Thanks again to Richard S and Richard B for their insightful reviews.
Roger
--

> -----Original Message-----
> From: Richard Sandiford <[email protected]>
> Sent: 29 June 2026 21:30
> To: Roger Sayle <[email protected]>
> Cc: 'Richard Biener' <[email protected]>; 'GCC Patches' <gcc-
> [email protected]>; 'Jeffrey Law' <[email protected]>
> Subject: Re: [PATCH] PR target/48609: RTL simplifications for x86 complex
arg
> passing.
> 
> "Roger Sayle" <[email protected]> writes:
> > Hi Richard and Richard,
> > Many thanks for the reviews.  Thanks for explaining that VEC_CONCAT is
> > in memory order, so that the first field needs to be SUBREG_BYTE (op)
> > == 0 rather than subreg_lowpart_p (op) [I'm fortunate enough that for
> > x86_64 both these conditions are equivalent].
> >
> > I'm currently tweaking my patch to avoid subreg_lowpart_p (except
> > where the inner mode and the outer mode have the same size, in which
> > case I believe subreg_lowpart_p is equivalent to SUBREG_BYTE == 0).
> >
> > However, to confirm that I now have this straight, do you agree that
> > the existing VEC_CONCAT transformation in simplify-rtx.cc (immediately
> > before my change) is broken/non-portable:
> >
> > simplify_rtx.cc line 5589
> >         /* (vec_concat:
> >              (subreg_lowpart:N OP)
> >              (vec_select:N OP P))  -->  OP when P selects the high half
> >             of the OP.  */
> >         if (GET_CODE (trueop0) == SUBREG
> >             && subreg_lowpart_p (trueop0)
> >             && GET_CODE (trueop1) == VEC_SELECT
> >             && SUBREG_REG (trueop0) == XEXP (trueop1, 0)
> >             && !side_effects_p (XEXP (trueop1, 0))
> >             && vec_series_highpart_p (op1_mode, mode, XEXP (trueop1,
1)))
> >           return XEXP (trueop1, 0);
> >
> > Notice the use of "subreg_lowpart_p (trueop0)" is the same idiom that
> > you're asking me to avoid/correct.
> 
> Yeah, I agree that that looks wrong.
> 
> Since that fold and yours are really variants of the same thing, I wonder
whether
> we should have an rtx equivalent of get_ref_base_and_extent.
> I've attached a proof of concept below.  It's completely untested, so
likely wrong
> in several ways, but I think it would be better than testing for specific
> combinations like doubly nested subregs, subregs of shifts, or
vec_selects.
> 
> We could later add support for other rtx codes, such as SIGN_EXTRACT,
> ZERO_EXTRACT, and TRUNCATE.
> 
> Thanks,
> Richard
> 
> 
> diff --git a/gcc/rtl.cc b/gcc/rtl.cc
> index 3839c364571..9e7390082c9 100644
> --- a/gcc/rtl.cc
> +++ b/gcc/rtl.cc
> @@ -594,12 +594,13 @@ rtvec_all_equal_p (const_rtvec vec)
>     { START, START+1, START+2, ... }.  */
> 
>  bool
> -rtvec_series_p (rtvec vec, int start)
> +rtvec_series_p (rtvec vec, poly_int64 start)
>  {
>    for (int i = 0; i < GET_NUM_ELEM (vec); i++)
>      {
> -      rtx x = RTVEC_ELT (vec, i);
> -      if (!CONST_INT_P (x) || INTVAL (x) != i + start)
> +      poly_int64 elt;
> +      if (!poly_int_rtx_p (RTVEC_ELT (vec, i), &elt)
> +       || maybe_ne (elt, i + start))
>       return false;
>      }
>    return true;
> diff --git a/gcc/rtl.h b/gcc/rtl.h
> index 747d917ecf2..55a7548e31f 100644
> --- a/gcc/rtl.h
> +++ b/gcc/rtl.h
> @@ -3085,7 +3085,7 @@ extern bool rtx_equal_p (const_rtx, const_rtx,
>                        rtx_equal_p_callback_function = NULL);
> 
>  extern bool rtvec_all_equal_p (const_rtvec); -extern bool rtvec_series_p
(rtvec,
> int);
> +extern bool rtvec_series_p (rtvec, poly_int64);
> 
>  /* Return true if X is a vector constant with a duplicated element value.
*/
> 
> diff --git a/gcc/rtlanal.cc b/gcc/rtlanal.cc index
5274a5c59cf..94a339b9a8a
> 100644
> --- a/gcc/rtlanal.cc
> +++ b/gcc/rtlanal.cc
> @@ -7100,3 +7100,76 @@ contains_paradoxical_subreg_p (rtx x)
>      }
>    return false;
>  }
> +
> +/* Analyze X as accessing a consecutive sequence of bytes in some base
rtx that
> +   is no smaller than X.  Return the base value and set *OFFSET_PTR to
the byte
> +   offset of X from the start of the base.  Like SUBREG_BYTE, this byte
offset
> +   follows memory order.  */
> +
> +rtx
> +get_ref_base_and_offset (rtx x, poly_uint64 *offset_ptr) {
> +  poly_uint64 outer_bytes = GET_MODE_SIZE (GET_MODE (x));
> +  poly_uint64 offset = 0;
> +  for (;;)
> +    {
> +      switch (GET_CODE (x))
> +     {
> +     case SUBREG:
> +       if (!paradoxical_subreg_p (x))
> +         {
> +           offset += SUBREG_BYTE (x);
> +           x = SUBREG_REG (x);
> +           continue;
> +         }
> +       break;
> +
> +     case ASHIFT:
> +     case LSHIFTRT:
> +     case ASHIFTRT:
> +       if (SCALAR_INT_MODE_P (GET_MODE (x))
> +           && CONST_INT_P (XEXP (x, 1))
> +           && UINTVAL (XEXP (x, 1)) % BITS_PER_UNIT == 0)
> +         {
> +           auto inner_bytes = GET_MODE_SIZE (GET_MODE (x));
> +           /* Reanalyze the current extraction as a shift right of X.  */
> +           poly_uint64 lsb = subreg_size_lsb (outer_bytes, inner_bytes,
> +                                              offset);
> +           /* Convert it to a shift right of XEXP (x, 0).  This might
> +              wrap.  */
> +           if (GET_CODE (x) == ASHIFT)
> +             lsb -= UINTVAL (XEXP (x, 1));
> +           else
> +             lsb += UINTVAL (XEXP (x, 1));
> +           if (known_le (lsb, (inner_bytes - outer_bytes) *
BITS_PER_UNIT))
> +             {
> +               x = XEXP (x, 0);
> +               offset = subreg_size_offset_from_lsb (outer_bytes,
> +                                                     inner_bytes, lsb);
> +               continue;
> +             }
> +         }
> +       break;
> +
> +     case VEC_SELECT:
> +       {
> +         rtx sel = XEXP (x, 1);
> +         poly_int64 start;
> +         if (poly_int_rtx_p (XVECEXP (sel, 0, 0), &start)
> +             && rtvec_series_p (XVEC (sel, 0), start))
> +           {
> +             x = XEXP (x, 0);
> +             offset += start * GET_MODE_UNIT_SIZE (GET_MODE (x));
> +             continue;
> +           }
> +         break;
> +       }
> +
> +     default:
> +       break;
> +     }
> +
> +      *offset_ptr = offset;
> +      return x;
> +    }
> +}
diff --git a/gcc/rtl.cc b/gcc/rtl.cc
index 3839c364571..dac230b2159 100644
--- a/gcc/rtl.cc
+++ b/gcc/rtl.cc
@@ -594,12 +594,13 @@ rtvec_all_equal_p (const_rtvec vec)
    { START, START+1, START+2, ... }.  */
 
 bool
-rtvec_series_p (rtvec vec, int start)
+rtvec_series_p (rtvec vec, poly_int64 start)
 {
   for (int i = 0; i < GET_NUM_ELEM (vec); i++)
     {
-      rtx x = RTVEC_ELT (vec, i);
-      if (!CONST_INT_P (x) || INTVAL (x) != i + start)
+      poly_int64 elt;
+      if (!poly_int_rtx_p (RTVEC_ELT (vec, i), &elt)
+          || maybe_ne (elt, i + start))
        return false;
     }
   return true;
diff --git a/gcc/rtl.h b/gcc/rtl.h
index 747d917ecf2..55a7548e31f 100644
--- a/gcc/rtl.h
+++ b/gcc/rtl.h
@@ -3085,7 +3085,7 @@ extern bool rtx_equal_p (const_rtx, const_rtx,
                         rtx_equal_p_callback_function = NULL);
 
 extern bool rtvec_all_equal_p (const_rtvec);
-extern bool rtvec_series_p (rtvec, int);
+extern bool rtvec_series_p (rtvec, poly_int64);
 
 /* Return true if X is a vector constant with a duplicated element value.  */
 
diff --git a/gcc/rtlanal.cc b/gcc/rtlanal.cc
index 5274a5c59cf..d897dc400ea 100644
--- a/gcc/rtlanal.cc
+++ b/gcc/rtlanal.cc
@@ -7057,14 +7057,12 @@ register_asm_p (const_rtx x)
 bool
 vec_series_highpart_p (machine_mode result_mode, machine_mode op_mode, rtx sel)
 {
-  int nunits;
-  if (GET_MODE_NUNITS (op_mode).is_constant (&nunits)
-      && targetm.can_change_mode_class (op_mode, result_mode, ALL_REGS))
-    {
-      int offset = BYTES_BIG_ENDIAN ? 0 : nunits - XVECLEN (sel, 0);
-      return rtvec_series_p (XVEC (sel, 0), offset);
-    }
-  return false;
+  if (!targetm.can_change_mode_class (op_mode, result_mode, ALL_REGS))
+    return false;
+  if (BYTES_BIG_ENDIAN)
+    return rtvec_series_p (XVEC (sel, 0), 0);
+  poly_int64 offset = GET_MODE_NUNITS (op_mode) - XVECLEN (sel, 0);
+  return rtvec_series_p (XVEC (sel, 0), offset);
 }
 
 /* Return true if, for all OP of mode OP_MODE:
@@ -7076,14 +7074,12 @@ vec_series_highpart_p (machine_mode result_mode, 
machine_mode op_mode, rtx sel)
 bool
 vec_series_lowpart_p (machine_mode result_mode, machine_mode op_mode, rtx sel)
 {
-  int nunits;
-  if (GET_MODE_NUNITS (op_mode).is_constant (&nunits)
-      && targetm.can_change_mode_class (op_mode, result_mode, ALL_REGS))
-    {
-      int offset = BYTES_BIG_ENDIAN ? nunits - XVECLEN (sel, 0) : 0;
-      return rtvec_series_p (XVEC (sel, 0), offset);
-    }
-  return false;
+  if (!targetm.can_change_mode_class (op_mode, result_mode, ALL_REGS))
+    return false;
+  if (!BYTES_BIG_ENDIAN)
+    return rtvec_series_p (XVEC (sel, 0), 0);
+  poly_int64 offset = GET_MODE_NUNITS (op_mode) - XVECLEN (sel, 0);
+  return rtvec_series_p (XVEC (sel, 0), offset);
 }
 
 /* Return true if X contains a paradoxical subreg.  */
@@ -7100,3 +7096,76 @@ contains_paradoxical_subreg_p (rtx x)
     }
   return false;
 }
+
+/* Analyze X as accessing a consecutive sequence of bytes in some base
+   rtx that is no smaller than X.  Return the base value and set *OFFSET_PTR
+   to the byte offset of X from the start of the base.  Like SUBREG_BYTE,
+   this byte offset follows memory order.  */
+
+rtx
+get_ref_base_and_offset (rtx x, poly_uint64 *offset_ptr)
+{
+  poly_uint64 outer_bytes = GET_MODE_SIZE (GET_MODE (x));
+  poly_uint64 offset = 0;
+  for (;;)
+    {
+      switch (GET_CODE (x))
+       {
+       case SUBREG:
+         if (!paradoxical_subreg_p (x))
+           {
+             offset += SUBREG_BYTE (x);
+             x = SUBREG_REG (x);
+             continue;
+           }
+         break;
+
+       case ASHIFT:
+       case LSHIFTRT:
+       case ASHIFTRT:
+         if (SCALAR_INT_MODE_P (GET_MODE (x))
+             && CONST_INT_P (XEXP (x, 1))
+             && UINTVAL (XEXP (x, 1)) % BITS_PER_UNIT == 0)
+           {
+             auto inner_bytes = GET_MODE_SIZE (GET_MODE (x));
+             /* Reanalyze the current extraction as a shift right of X.  */
+             poly_uint64 lsb = subreg_size_lsb (outer_bytes, inner_bytes,
+                                                offset);
+             /* Convert it to a shift right of XEXP (x, 0).  This might
+                wrap.  */
+             if (GET_CODE (x) == ASHIFT)
+               lsb -= UINTVAL (XEXP (x, 1));
+             else
+               lsb += UINTVAL (XEXP (x, 1));
+             if (known_le (lsb, (inner_bytes - outer_bytes) * BITS_PER_UNIT))
+               {
+                 x = XEXP (x, 0);
+                 offset = subreg_size_offset_from_lsb (outer_bytes,
+                                                       inner_bytes, lsb);
+                 continue;
+               }
+           }
+         break;
+
+       case VEC_SELECT:
+         {
+           rtx sel = XEXP (x, 1);
+           poly_int64 start;
+           if (poly_int_rtx_p (XVECEXP (sel, 0, 0), &start)
+               && rtvec_series_p (XVEC (sel, 0), start))
+             {
+               x = XEXP (x, 0);
+               offset += start * GET_MODE_UNIT_SIZE (GET_MODE (x));
+               continue;
+             }
+           break;
+         }
+
+       default:
+         break;
+      }
+
+    *offset_ptr = offset;
+     return x;
+   }
+}
diff --git a/gcc/rtlanal.h b/gcc/rtlanal.h
index 04c3b1b9a59..99addaa80ff 100644
--- a/gcc/rtlanal.h
+++ b/gcc/rtlanal.h
@@ -336,8 +336,12 @@ vec_series_highpart_p (machine_mode result_mode, 
machine_mode op_mode,
                       rtx sel);
 
 bool
-vec_series_lowpart_p (machine_mode result_mode, machine_mode op_mode, rtx sel);
+vec_series_lowpart_p (machine_mode result_mode, machine_mode op_mode,
+                     rtx sel);
 
 bool
 contains_paradoxical_subreg_p (rtx x);
+
+rtx
+get_ref_base_and_offset (rtx x, poly_uint64 *offset_ptr);
 #endif
diff --git a/gcc/simplify-rtx.cc b/gcc/simplify-rtx.cc
index 92a2a6e954a..32267ac061b 100644
--- a/gcc/simplify-rtx.cc
+++ b/gcc/simplify-rtx.cc
@@ -5586,17 +5586,30 @@ simplify_ashift:
            return simplify_gen_binary (VEC_SELECT, mode, XEXP (trueop0, 0),
                                        gen_rtx_PARALLEL (VOIDmode, vec));
          }
-       /* (vec_concat:
-            (subreg_lowpart:N OP)
-            (vec_select:N OP P))  -->  OP when P selects the high half
-           of the OP.  */
-       if (GET_CODE (trueop0) == SUBREG
-           && subreg_lowpart_p (trueop0)
-           && GET_CODE (trueop1) == VEC_SELECT
-           && SUBREG_REG (trueop0) == XEXP (trueop1, 0)
-           && !side_effects_p (XEXP (trueop1, 0))
-           && vec_series_highpart_p (op1_mode, mode, XEXP (trueop1, 1)))
-         return XEXP (trueop1, 0);
+       /* (vec_concat:N
+            (subreg:N/2 OP 0)
+            (subreg:N/2 OP N/2)) --> OP
+          i.e. where concatenating the first and second halves of the
+          same object OP.  */
+       if (known_eq (GET_MODE_SIZE (op0_mode),
+                     GET_MODE_SIZE (op1_mode)))
+         {
+           poly_uint64 offset = 0u;
+           rtx base0 = get_ref_base_and_offset (trueop0, &offset);
+           if (base0
+               && known_eq (offset, 0u)
+               && known_eq (GET_MODE_SIZE (GET_MODE (base0)),
+                            GET_MODE_SIZE (mode)))
+             {
+               rtx base1 = get_ref_base_and_offset (trueop1, &offset);
+               if (base1
+                   && rtx_equal_p (base0, base1)
+                   && known_eq (offset, GET_MODE_SIZE (op0_mode))
+                   && !side_effects_p (trueop0)
+                   && !side_effects_p (trueop1))
+                 return gen_lowpart (mode, base0);
+             }
+         }
       }
       return 0;
 
diff --git a/gcc/testsuite/gcc.target/i386/pr48609.c 
b/gcc/testsuite/gcc.target/i386/pr48609.c
new file mode 100644
index 00000000000..8af9d883761
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr48609.c
@@ -0,0 +1,13 @@
+/* PR target/48609 */
+/* { dg-do compile { target { ! ia32 } } } */
+/* { dg-options "-O2 -msse2" } */
+typedef _Complex float SCtype;
+extern SCtype bar;
+void foo (SCtype x)
+{
+  bar = x;
+}
+
+/* { dg-final { scan-assembler-not "movdqa" } } */
+/* { dg-final { scan-assembler-not "shufps" } } */
+/* { dg-final { scan-assembler-not "unpcklps" } } */

Reply via email to