On Mon, Jun 01, 2026 at 12:34:15PM -0700, Andrew Pinski wrote:
> On Mon, Jun 1, 2026 at 7:35 AM Dylan Rees <[email protected]> wrote:
> >
> > The fix to improve the folding of low-to-highpart builtins resulted
> > in redundant duplication instructions. The compiler was placing the
> > uniform vector arguments for low/highpart intrinsics in separate
> > registers despite them duplicating the same scalar value. This turns
> > out to be a result of the ordering of the duplication instructions,
> > when a narrow duplication of a scalar value appears before a wider
> > duplication of that same scalar the compiler is unable to link them
> > together into a single operation.
> >
> > It is desirable in this case to create a single wider vector and
> > take the lower half for narrower uses of the same vector, since we
> > can then use a single load instruction. This patch fixes this logic
> > within CSE by matching vector duplicates during 'cse_prescan', if
> > they are duplicating the same pseudo into vectors of different
> > widths but the same element size. In 'cse_main' these matches are
> > processed and the wider vector duplicate insn is emitted before
> > all matching duplications, narrower duplicate insns will
> > be converted to a SUBREG of this initial duplication and duplicates
> > of the same width will reuse it.
> >
> > Two test cases were updated to be more flexible with regexs since
> > hardcoded values were causing unnecessary failures despite the
> > code being logically correct.
> 
> I am curious if this makes part of the x86_cse (the
> remove_redundant_vector part) not do as much.
I’m not entirely sure about this either, but until recently there were          
                                            
some x86_cse regression failures. They disappeared, so I also wonder            
                                            
whether there is some overlap between the two.
> I like the idea of a generic pass to handle this cases rather than a
> target specific one.
> I have not checked but does this handle constants and not just
> vec_duplicates? E.g. 0 or -1 would seem to good to handle too.
> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99639 is another example
> where constants can show up.
No, this patch doesn’t handle constants, only vector duplicates.                
                                            
However, I agree that this structure could be extended to address those         
                                            
cases as well. 
> 
> Thanks,
> Andrea
Thanks,
Dylan
> 
> >
> > gcc/ChangeLog:
> >
> >         * cse.cc (struct cse_vec_duplicate_match): New struct.
> >         (struct cse_basic_block_data): New member added.
> >         (cse_prescan_cache_vec_dup): New function to cache any vector
> >         duplications and check for potential matches.
> >         (cse_prescan_path): Extended pre_scan loop to cache vector
> >         duplication matches + New loop added to assign a list of
> >         matches to a block of data inside new struct member.
> >         (cse_main): New loop to emit wider vector duplication earlier
> >         and update narrower vector duplicate to SUBREG insn.
> >
> > gcc/testsuite/ChangeLog:
> >
> >         * gcc.target/aarch64/simd/fold_to_highpart_7.c:
> >         Extended test.
> >         * gcc.target/i386/pr81501-9a.c: Updated test.
> >         * gcc.target/i386/pr81501-9b.c: Updated test.
> > ---
> >  gcc/cse.cc                                    | 239 +++++++++++++++++-
> >  .../aarch64/simd/fold_to_highpart_7.c         |   8 +-
> >  gcc/testsuite/gcc.target/i386/pr81501-9a.c    |   4 +-
> >  gcc/testsuite/gcc.target/i386/pr81501-9b.c    |   2 +-
> >  4 files changed, 244 insertions(+), 9 deletions(-)
> >
> > diff --git a/gcc/cse.cc b/gcc/cse.cc
> > index c1e0cc2167c..6c0e5d879bc 100644
> > --- a/gcc/cse.cc
> > +++ b/gcc/cse.cc
> > @@ -483,6 +483,39 @@ struct branch_path
> >    basic_block bb;
> >  };
> >
> > +/* This data describes a pair of vec_duplicates in the same BB, which 
> > duplicate
> > +   the same pseudo to different vector lengths.  The same structure is also
> > +   used while prescanning a basic block as a temporary cache entry.  */
> > +
> > +struct cse_vec_duplicate_match
> > +{
> > +  basic_block bb;
> > +  machine_mode widest_mode;
> > +  rtx scalar;
> > +  rtx_insn *first_insn;
> > +  rtx_insn *widest_insn;
> > +  auto_vec<rtx_insn *> related_dups;
> > +
> > +  cse_vec_duplicate_match () = default;
> > +
> > +  cse_vec_duplicate_match (basic_block bb_, machine_mode widest_mode_,
> > +                          rtx scalar_, rtx_insn *first_insn_,
> > +                          rtx_insn *widest_insn_)
> > +    : bb (bb_), widest_mode (widest_mode_), scalar (scalar_),
> > +      first_insn (first_insn_), widest_insn (widest_insn_)
> > +  {}
> > +
> > +  cse_vec_duplicate_match (const cse_vec_duplicate_match &other)
> > +    : bb (other.bb), widest_mode (other.widest_mode), scalar 
> > (other.scalar),
> > +      first_insn (other.first_insn), widest_insn (other.widest_insn)
> > +  {
> > +    unsigned int i;
> > +    rtx_insn *insn;
> > +    FOR_EACH_VEC_ELT (other.related_dups, i, insn)
> > +      related_dups.safe_push (insn);
> > +  }
> > +};
> > +
> >  /* This data describes a block that will be processed by
> >     cse_extended_basic_block.  */
> >
> > @@ -494,6 +527,10 @@ struct cse_basic_block_data
> >    int path_size;
> >    /* Current path, indicating which basic_blocks will be processed.  */
> >    struct branch_path *path;
> > +  /* vec_duplicate sources seen in the current BB while prescanning.  */
> > +  auto_vec<cse_vec_duplicate_match, 8> vec_duplicate_cache;
> > +  /* Syntactic vec_duplicate matches found in the same BB while 
> > prescanning.  */
> > +  auto_vec<cse_vec_duplicate_match, 8> vec_duplicate_matches;
> >  };
> >
> >
> > @@ -545,6 +582,8 @@ static rtx equiv_constant (rtx);
> >  static void record_jump_equiv (rtx_insn *, bool);
> >  static void record_jump_cond (enum rtx_code, machine_mode, rtx, rtx);
> >  static void cse_insn (rtx_insn *);
> > +static void cse_prescan_cache_vec_dup (struct cse_basic_block_data *,
> > +                                      basic_block, rtx_insn *, rtx);
> >  static void cse_prescan_path (struct cse_basic_block_data *);
> >  static void invalidate_from_clobbers (rtx_insn *);
> >  static void invalidate_from_sets_and_clobbers (rtx_insn *);
> > @@ -6527,9 +6566,63 @@ have_eh_succ_edges (basic_block bb)
> >    return false;
> >  }
> >
> > +/* Record vec_duplicate match for SET in the same basic block, if any.  */
> > +static void
> > +cse_prescan_cache_vec_dup (struct cse_basic_block_data *data, basic_block 
> > bb,
> > +                          rtx_insn *insn, rtx set)
> > +{
> > +  rtx src = SET_SRC (set);
> > +  rtx dest = SET_DEST (set);
> > +  machine_mode mode = GET_MODE (src);
> > +  rtx scalar;
> > +
> > +  /* Limit matching to duplicates of the same pseudo register, or an exact
> > +     SUBREG of such a pseudo.  */
> > +  if (!vec_duplicate_p (src, &scalar)
> > +      || !(REG_P (scalar)
> > +          ? REGNO (scalar) >= FIRST_PSEUDO_REGISTER
> > +          : (GET_CODE (scalar) == SUBREG
> > +             && REG_P (SUBREG_REG (scalar))
> > +             && REGNO (SUBREG_REG (scalar)) >= FIRST_PSEUDO_REGISTER)))
> > +    return;
> > +
> > +  /* Check for matching cached vec_duplicates and save the match if found. 
> >  */
> > +  for (unsigned int i = 0; i < data->vec_duplicate_cache.length (); i++)
> > +    {
> > +      cse_vec_duplicate_match &entry = data->vec_duplicate_cache[i];
> > +
> > +      /* Create a match with existing cache entry if both scalar register
> > +        pseudos are matching and the new vec_duplicate mode is wider.  */
> > +      if (rtx_equal_p (entry.scalar, scalar)
> > +         && known_gt (GET_MODE_SIZE (mode), GET_MODE_SIZE 
> > (entry.widest_mode))
> > +         && GET_MODE_INNER (mode) == GET_MODE_INNER (entry.widest_mode)
> > +         && REG_P (dest))
> > +       {
> > +         entry.widest_mode = mode;
> > +         entry.widest_insn = insn;
> > +         entry.related_dups.safe_push (insn);
> > +         return;
> > +       }
> > +      else if (rtx_equal_p (entry.scalar, scalar)
> > +              && known_le (GET_MODE_SIZE (mode),
> > +                           GET_MODE_SIZE (entry.widest_mode))
> > +              && GET_MODE_INNER (mode) == GET_MODE_INNER 
> > (entry.widest_mode))
> > +       {
> > +         entry.related_dups.safe_push (insn);
> > +         return;
> > +       }
> > +    }
> > +
> > +  /* Cache vec_duplicate as a new entry if no match was found.  */
> > +  cse_vec_duplicate_match new_entry (bb, mode, scalar, insn, NULL);
> > +  new_entry.related_dups.safe_push (insn);
> > +  data->vec_duplicate_cache.safe_push (new_entry);
> > +}
> > +
> >
> >  /* Scan to the end of the path described by DATA.  Return an estimate of
> > -   the total number of SETs of all insns in the path.  */
> > +   the total number of SETs of all insns in the path.  Also record any
> > +   matching vec_duplicate SETs of the same scalar value for different 
> > modes.  */
> >
> >  static void
> >  cse_prescan_path (struct cse_basic_block_data *data)
> > @@ -6538,6 +6631,8 @@ cse_prescan_path (struct cse_basic_block_data *data)
> >    int path_size = data->path_size;
> >    int path_entry;
> >
> > +  data->vec_duplicate_matches.truncate (0);
> > +
> >    /* Scan to end of each basic block in the path.  */
> >    for (path_entry = 0; path_entry < path_size; path_entry++)
> >      {
> > @@ -6545,18 +6640,47 @@ cse_prescan_path (struct cse_basic_block_data *data)
> >        rtx_insn *insn;
> >
> >        bb = data->path[path_entry].bb;
> > +      data->vec_duplicate_cache.truncate (0);
> >
> >        FOR_BB_INSNS (bb, insn)
> >         {
> >           if (!INSN_P (insn))
> >             continue;
> >
> > +         rtx pattern = PATTERN (insn);
> > +
> >           /* A PARALLEL can have lots of SETs in it,
> >              especially if it is really an ASM_OPERANDS.  */
> > -         if (GET_CODE (PATTERN (insn)) == PARALLEL)
> > -           nsets += XVECLEN (PATTERN (insn), 0);
> > +         if (GET_CODE (pattern) == PARALLEL)
> > +           {
> > +             int len = XVECLEN (pattern, 0);
> > +
> > +             nsets += len;
> > +             for (int i = 0; i < len; i++)
> > +               {
> > +                 rtx elt = XVECEXP (pattern, 0, i);
> > +                 if (GET_CODE (elt) == SET)
> > +                   cse_prescan_cache_vec_dup (data, bb, insn, elt);
> > +               }
> > +           }
> >           else
> > -           nsets += 1;
> > +           {
> > +             if (GET_CODE (pattern) == SET)
> > +               cse_prescan_cache_vec_dup (data, bb, insn, pattern);
> > +             nsets += 1;
> > +           }
> > +       }
> > +
> > +      /* Record any vec_duplicate matches from this basic block.  */
> > +      for (unsigned int i = 0; i < data->vec_duplicate_cache.length (); 
> > i++)
> > +       {
> > +         cse_vec_duplicate_match &entry = data->vec_duplicate_cache[i];
> > +
> > +         /* If we recorded no wider vec_duplicate match then skip.  */
> > +         if (entry.widest_insn == NULL)
> > +           continue;
> > +
> > +         data->vec_duplicate_matches.safe_push (entry);
> >         }
> >      }
> >
> > @@ -6803,6 +6927,113 @@ cse_main (rtx_insn *f ATTRIBUTE_UNUSED, int nregs)
> >           if (ebb_data.nsets == 0)
> >             continue;
> >
> > +       /* If prescan discovers any vec_duplicate pairs where a wider 
> > duplicate
> > +          appears after a narrow duplicate of the same scalar, move the
> > +          widest duplicate before the first vec_duplicate of this scalar 
> > and
> > +          rewrite all related duplicates to reuse it.  */
> > +       for (unsigned int i = 0;
> > +            i < ebb_data.vec_duplicate_matches.length ();
> > +            i++)
> > +         {
> > +           cse_vec_duplicate_match &match = 
> > ebb_data.vec_duplicate_matches[i];
> > +
> > +           rtx wide_set = single_set (match.widest_insn);
> > +           if (!wide_set)
> > +             continue;
> > +           rtx wide_reg = SET_DEST (wide_set);
> > +           rtx_insn *insert_after = PREV_INSN (match.first_insn);
> > +           if (!REG_P (wide_reg) || !insert_after)
> > +             continue;
> > +
> > +           /* Safety check that WIDE_REG is not otherwise used or redefined
> > +              before its original defining insn.  */
> > +           if (reg_used_between_p (wide_reg, match.first_insn,
> > +                                   match.widest_insn)
> > +               || reg_set_between_p (wide_reg, match.first_insn,
> > +                                     match.widest_insn))
> > +             continue;
> > +
> > +           /* Rewrite all related duplicates as a group so we either keep 
> > the
> > +              whole transformation or none of it.  */
> > +           int prev_changes = num_changes_pending ();
> > +           bool abort_changes = false;
> > +           unsigned int j;
> > +           rtx_insn *dup_insn;
> > +
> > +           FOR_EACH_VEC_ELT (match.related_dups, j, dup_insn)
> > +             {
> > +               rtx dup_set;
> > +               rtx dup_src;
> > +               machine_mode dup_mode;
> > +
> > +               if (dup_insn == match.widest_insn)
> > +                 continue;
> > +
> > +               /* The earlier safety check already covered the range from
> > +                  FIRST_INSN up to WIDEST_INSN.  Only check the remaining
> > +                  suffix for duplicates that come later in the block.  This
> > +                  also covers call-clobbered hard registers via
> > +                  reg_set_between_p.  */
> > +               for (rtx_insn *scan = NEXT_INSN (match.widest_insn);
> > +                    scan != NULL_RTX; scan = NEXT_INSN (scan))
> > +                 if (scan == dup_insn)
> > +                   {
> > +                     if (reg_set_between_p (wide_reg,
> > +                                            match.widest_insn,
> > +                                            dup_insn))
> > +                       continue;
> > +                     break;
> > +                   }
> > +
> > +
> > +               dup_set = single_set (dup_insn);
> > +               if (!dup_set)
> > +                 {
> > +                   abort_changes = true;
> > +                   break;
> > +                 }
> > +
> > +               dup_mode = GET_MODE (SET_DEST (dup_set));
> > +               if (dup_mode == GET_MODE (wide_reg))
> > +                 dup_src = copy_rtx (wide_reg);
> > +               else
> > +                 dup_src = gen_lowpart (dup_mode, wide_reg);
> > +
> > +               if (!dup_src)
> > +                 {
> > +                   abort_changes = true;
> > +                   break;
> > +                 }
> > +
> > +               if (rtx_equal_p (dup_src, SET_DEST (dup_set)))
> > +                 continue;
> > +
> > +               if (!validate_change (dup_insn, &SET_SRC (dup_set), 
> > dup_src, 1))
> > +                 {
> > +                   abort_changes = true;
> > +                   break;
> > +                 }
> > +             }
> > +
> > +           if (abort_changes)
> > +             {
> > +               cancel_changes (prev_changes);
> > +               continue;
> > +             }
> > +
> > +           if (num_changes_pending () == prev_changes)
> > +             continue;
> > +
> > +           if (!apply_change_group ())
> > +             continue;
> > +
> > +           reorder_insns (match.widest_insn, match.widest_insn, 
> > insert_after);
> > +
> > +           FOR_EACH_VEC_ELT (match.related_dups, j, dup_insn)
> > +             if (dup_insn != match.widest_insn)
> > +               df_insn_rescan (dup_insn);
> > +       }
> > +
> >           /* Get a reasonable estimate for the maximum number of qty's
> >              needed for this path.  For this, we take the number of sets
> >              and multiply that by MAX_RECOG_OPERANDS.  */
> > diff --git a/gcc/testsuite/gcc.target/aarch64/simd/fold_to_highpart_7.c 
> > b/gcc/testsuite/gcc.target/aarch64/simd/fold_to_highpart_7.c
> > index 941dab3e9b9..ae7c368da8c 100644
> > --- a/gcc/testsuite/gcc.target/aarch64/simd/fold_to_highpart_7.c
> > +++ b/gcc/testsuite/gcc.target/aarch64/simd/fold_to_highpart_7.c
> > @@ -4,7 +4,9 @@
> >  #include <arm_neon.h>
> >
> >  /* We should fold to the highpart builtin when the multiplying the 
> > highpart of a
> > -   128b vector with a uniform vector which can be widened.
> > +   128b vector with a uniform vector. Also the uniform vector should be 
> > loaded
> > +   as a single wider vector duplicate load (ld1r) using the lower half for 
> > the
> > +   lowpart builtin and the full vector duplicate for the highpart builtin.
> >
> >     Use vdup_n_u8 to create an 8x8 splat vector.  */
> >
> > @@ -15,4 +17,6 @@ uint16x8_t foo(uint8_t *a, uint8_t *b) {
> >  }
> >
> >  /* { dg-final { scan-assembler {mull2\t} } } */
> > -/* { dg-final { scan-assembler-not {mull\t} } } */
> > \ No newline at end of file
> > +/* { dg-final { scan-assembler-not {mull\t} } } */
> > +/* { dg-final { scan-assembler {\tld1r\t} } } */
> > +/* { dg-final { scan-assembler-not {\tdup\tv} } } */
> > diff --git a/gcc/testsuite/gcc.target/i386/pr81501-9a.c 
> > b/gcc/testsuite/gcc.target/i386/pr81501-9a.c
> > index 66a2768a22c..ea7772dbb21 100644
> > --- a/gcc/testsuite/gcc.target/i386/pr81501-9a.c
> > +++ b/gcc/testsuite/gcc.target/i386/pr81501-9a.c
> > @@ -7,10 +7,10 @@
> >  **foo:
> >  **.LFB[0-9]+:
> >  **...
> > -**     vpbroadcastb    %edi, %zmm0
> > -**...
> >  **     call    __tls_get_addr@PLT
> >  **...
> > +**     vpbroadcastb    %(edi|ebx), %zmm\d
> > +**...
> >  */
> >
> >  #include <immintrin.h>
> > diff --git a/gcc/testsuite/gcc.target/i386/pr81501-9b.c 
> > b/gcc/testsuite/gcc.target/i386/pr81501-9b.c
> > index 711b177bc1e..bd1b65d3e87 100644
> > --- a/gcc/testsuite/gcc.target/i386/pr81501-9b.c
> > +++ b/gcc/testsuite/gcc.target/i386/pr81501-9b.c
> > @@ -8,7 +8,7 @@
> >  **foo:
> >  **.LFB[0-9]+:
> >  **...
> > -**     vpbroadcastb    %edi, %zmm0
> > +**     vpbroadcastb    %(edi|r\d+d), %zmm\d
> >  **...
> >  **     lea(l|q)        var@TLSDESC\(%rip\), %(e|r)ax
> >  **...
> > --
> > 2.43.0
> >

Reply via email to