On Thu, Jun 25, 2026 at 7:28 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.
>
> 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 | 231 +++++++++++++++++-
> .../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, 237 insertions(+), 8 deletions(-)
>
> diff --git a/gcc/cse.cc b/gcc/cse.cc
> index c1e0cc2167c..46061520c46 100644
> --- a/gcc/cse.cc
> +++ b/gcc/cse.cc
> @@ -483,6 +483,35 @@ 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 (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)
> + {
> + related_dups.reserve(other.related_dups.length ());
> + related_dups.splice(other.related_dups);
> + }
> +};
> +
> /* This data describes a block that will be processed by
> cse_extended_basic_block. */
>
> @@ -494,6 +523,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 +578,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 +6562,60 @@ 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 (auto &entry : data->vec_duplicate_cache)
> + {
> + /* Create a match with existing cache entry if both scalar register
> + pseudos are matching and the new vec_duplicate mode is wider. */
> + if (REG_P (dest) && 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))
> + {
> + entry.widest_mode = mode;
> + entry.widest_insn = insn;
> + entry.related_dups.safe_push (insn);
> + return;
> + }
> + else if (GET_MODE_INNER (mode) == GET_MODE_INNER (entry.widest_mode)
> + && rtx_equal_p (entry.scalar, scalar)
> + && known_le (GET_MODE_SIZE (mode),
> + GET_MODE_SIZE (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 +6624,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 +6633,45 @@ 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 (auto &entry : data->vec_duplicate_cache)
> + {
> + /* If we recorded no wider vec_duplicate match then skip. */
> + if (entry.widest_insn == NULL_RTX)
> + continue;
> +
> + data->vec_duplicate_matches.safe_push (entry);
> }
> }
>
> @@ -6803,6 +6918,114 @@ 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 (auto &match : ebb_data.vec_duplicate_matches)
> + {
> + 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;
> +
> + for (auto &dup_insn : match.related_dups)
> + {
> + 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. */
> + bool dup_after_widest_insn = false;
> + for (rtx_insn *scan = NEXT_INSN (match.widest_insn);
> + scan != NULL_RTX; scan = NEXT_INSN (scan))
> + {
> + if (BLOCK_FOR_INSN(scan) != match.bb)
> + break;
I think this check might cause rtl check failures (I could be wrong).
Note there is also a missing space before the `(`.
I think it is better just to do:
for (rtx_insn *scan = NEXT_INSN (match.widest_insn);
scan != NEXT_INSN (BB_END (match.bb)) && scan != NULL_RTX;
scan = NEXT_INSN (scan))
As it is done elsewhere.
> +
> + if (scan == dup_insn)
> + {
> + dup_after_widest_insn = true;
> + break;
> + }
> + }
> +
> + if (dup_after_widest_insn
> + && reg_set_between_p (wide_reg, match.widest_insn,
> + dup_insn))
> + continue;
This has a double scan of the INSNs.
So why not just:
```
bool reg_set = false;
bool reg_set_with_widest_after = false;
for (rtx_insn *scan = NEXT_INSN (match.widest_insn);
scan != NEXT_INSN (BB_END (match.bb)) && scan != NULL_RTX;
scan = NEXT_INSN (scan))
{
if (scan == dup_insn)
{
reg_set_with_widest_after = reg_set;
break;
}
if (!reg_set && INSN_P (insn) && reg_set_p (wide_reg, scan))
reg_set = true;
}
if (reg_set_with_widest_after)
continue;
```
Yes reg_set_p might be called a few extra times if dup insn is not
after the widest insn, but walking the insn twice is worse I suspect.
Thanks,
Andrea
> +
> + 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 = 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);
> +
> + if (match.related_dups.contains (match.widest_insn))
> + df_insn_rescan (match.widest_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 d945566b043..5a184312716 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
> @@ -5,13 +5,17 @@
> #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. */
>
> /*
> ** foo:
> ** ...
> +** ld1r {v([0-9]+).16b}, \[x([0-9]+)\]
> +** ...
> ** umull2 v([0-9]+).8h, v([0-9]+).16b, v([0-9]+).16b
> ** ...
> */
> @@ -20,3 +24,5 @@ uint16x8_t foo(uint8_t *a, uint8_t *b) {
> const uint8x16_t hipart_vec = vld1q_u8(b);
> return vmull_u8(vget_high_u8(hipart_vec), uniform_vec);
> }
> +
> +/* { 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
>