> -----Original Message-----
> From: Christopher Bazley via Sourceware Forge <forge-
> [email protected]>
> Sent: 29 July 2026 21:57
> To: gcc-patches mailing list <[email protected]>
> Cc: Tamar Christina <[email protected]>
> Subject: [PATCH v2 1/1] AArch64: Improve costing of truncated stores
>
> From: Christopher Bazley <[email protected]>
>
> PR target/126480
>
> The following pattern of truncating assignments whose results are
> consumed only by store operations is relatively common:
>
> dst[0] = (unsigned char) src[0];
> dst[1] = (unsigned char) src[1];
> ....
> dst[N] = (unsigned char) src[N];
>
> Prior to this change, the vectorizer estimated unrealistically high
> costs for some scalar code: a cost was charged for each narrowing
> conversion, even though those conversions are effectively free as
> part of the associated stores. Consequently, the vectorizer could
> decide to vectorize code that should not have been vectorized.
>
> Scalar costs are inevitably somewhat overestimated in the case of
> byte order reversals that should cause GCC to generate a 'rev'
> instruction, because the vectorizer estimates costs independently of
> the store-merging pass that discovers such reversals in scalar code.
> When predicated tails are enabled for basic block SLP, the scalar cost
> of reversals can be overestimated by so much that they are vectorized.
> That will not happen after this change is applied.
>
> The AArch64 backend now uses a new vectorizer function,
> vect_is_truncating_store, to tell whether a given stmt truncates the
> input of a store. This function is analogous to an existing
> function, vect_is_extending_load, which tells whether a given stmt
> extends the result of a load. The two functions are called in
> roughly the same places, to help with the accuracy of costing scalar
> and vector stmts.
>
> A truncating assignment that has multiple uses should not be in an
> SLP tree being costed, but it seems convenient to use single_imm_use
> anyway (and it fits the expected/desired case we need to identify).
>
> gcc/ChangeLog:
>
> * config/aarch64/aarch64.cc (aarch64_detect_scalar_stmt_subtype):
> Call the new vect_is_truncating_store function and return 0 if
> vect_is_truncating_store returns true.
> (aarch64_sve_adjust_stmt_cost): Call vect_is_truncating_store
> and assign 0 to stmt_cost if vect_is_truncating_store returns
> true.
> * tree-vectorizer.h (vect_is_truncating_store): New function
> analogous to vect_is_extending_load.
>
> gcc/testsuite/ChangeLog:
>
> * gcc.target/aarch64/pr126480.c: New test.
OK.
Thanks,
Tamar
> ---
> gcc/config/aarch64/aarch64.cc | 16 +++++++++---
> gcc/testsuite/gcc.target/aarch64/pr126480.c | 20 ++++++++++++++
> gcc/tree-vectorizer.h | 29 +++++++++++++++++++++
> 3 files changed, 62 insertions(+), 3 deletions(-)
> create mode 100644 gcc/testsuite/gcc.target/aarch64/pr126480.c
>
> diff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc
> index 93c00c23a50d..d19ca305d829 100644
> --- a/gcc/config/aarch64/aarch64.cc
> +++ b/gcc/config/aarch64/aarch64.cc
> @@ -18247,9 +18247,12 @@ aarch64_detect_scalar_stmt_subtype
> (vec_info *vinfo, vect_cost_for_stmt kind,
> stmt_vec_info stmt_info,
> fractional_cost stmt_cost)
> {
> - /* Detect an extension of a loaded value. In general, we'll be able to
> fuse
> - the extension with the load. */
> - if (kind == scalar_stmt && vect_is_extending_load (vinfo, stmt_info))
> + /* Detect an extension of a loaded value or truncation of a value being
> + stored. In general, we'll be able to fuse the extension/truncation with
> + the load/store. */
> + if (kind == scalar_stmt
> + && (vect_is_extending_load (vinfo, stmt_info)
> + || vect_is_truncating_store (vinfo, stmt_info)))
> return 0;
>
> return stmt_cost;
> @@ -18377,6 +18380,13 @@ aarch64_sve_adjust_stmt_cost (class vec_info
> *vinfo, vect_cost_for_stmt kind,
> if (kind == vector_stmt && vect_is_extending_load (vinfo, stmt_info))
> stmt_cost = 0;
>
> + /* Most stores have truncating forms that can do the truncation on the fly.
> + Optimistically assume that a truncation followed by a store will fold to
> + this form during combine, and that the truncation therefore comes for
> free.
> + */
> + if (kind == vector_stmt && vect_is_truncating_store (vinfo, stmt_info))
> + stmt_cost = 0;
> +
> /* For similar reasons, vector_stmt integer truncations are a no-op,
> because we can just ignore the unused upper bits of the source. */
> if (kind == vector_stmt && vect_is_integer_truncation (stmt_info))
> diff --git a/gcc/testsuite/gcc.target/aarch64/pr126480.c
> b/gcc/testsuite/gcc.target/aarch64/pr126480.c
> new file mode 100644
> index 000000000000..e0b55130250e
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/pr126480.c
> @@ -0,0 +1,20 @@
> +/* PR target/126480 */
> +/* { dg-do compile } */
> +/* { dg-additional-options "-O2 -march=armv8.2-a -fdump-tree-slp2-details"
> } */
> +
> +void
> +foo (unsigned int *foo, unsigned long *a, unsigned long *b)
> +{
> + foo[0] = a[0] >> 33;
> + foo[1] = a[0] >> 44;
> +}
> +
> +/* If costs are pessimistically charged for scalar truncations, then the
> + function is vectorized. Truncation is expected to be free as part of the
> + store operations, therefore the scalar alternative is expected to be more
> + efficient than the vectorized version. */
> +/* { dg-final { scan-tree-dump-not "basic block part vectorized" "slp2" } }
> */
> +
> +/* { dg-final { scan-assembler-not {\tushl\tv[0-9]+.2d, v[0-9]+.2d, v[0-
> 9]+.2d\n} } } */
> +/* { dg-final { scan-assembler-times {\tlsr\tx[0-9]+, x[0-9]+, 33\n} 1 } } */
> +/* { dg-final { scan-assembler-times {\tlsr\tx[0-9]+, x[0-9]+, 44\n} 1 } } */
> diff --git a/gcc/tree-vectorizer.h b/gcc/tree-vectorizer.h
> index d4eb20f1a107..94ef81383f42 100644
> --- a/gcc/tree-vectorizer.h
> +++ b/gcc/tree-vectorizer.h
> @@ -31,6 +31,7 @@ typedef struct _slp_tree *slp_tree;
> #include "tree-ssa-operands.h"
> #include "gimple-match.h"
> #include "dominance.h"
> +#include "ssa.h"
>
> /* Used for naming of new temporaries. */
> enum vect_var_kind {
> @@ -3000,6 +3001,34 @@ vect_is_extending_load (class vec_info *vinfo,
> stmt_vec_info stmt_info)
> && DR_IS_READ (STMT_VINFO_DATA_REF (def_stmt_info)));
> }
>
> +/* Return true if STMT_INFO truncates the input of a store. */
> +inline bool
> +vect_is_truncating_store (class vec_info *vinfo, stmt_vec_info stmt_info)
> +{
> + /* Although this is quite large for an inline function, this part
> + at least should be inline. */
> + gassign *assign = dyn_cast<gassign *> (stmt_info->stmt);
> + if (!assign || !CONVERT_EXPR_CODE_P (gimple_assign_rhs_code (assign)))
> + return false;
> +
> + tree rhs = gimple_assign_rhs1 (stmt_info->stmt);
> + tree lhs = gimple_assign_lhs (assign);
> + tree lhs_type = TREE_TYPE (lhs);
> + tree rhs_type = TREE_TYPE (rhs);
> + if (!INTEGRAL_TYPE_P (lhs_type) || !INTEGRAL_TYPE_P (rhs_type)
> + || TYPE_PRECISION (lhs_type) >= TYPE_PRECISION (rhs_type))
> + return false;
> +
> + gimple *use_stmt;
> + use_operand_p use_p;
> + if (!single_imm_use (lhs, &use_p, &use_stmt))
> + return false;
> +
> + stmt_vec_info use_stmt_info = vinfo->lookup_stmt (use_stmt);
> + return (use_stmt_info && STMT_VINFO_DATA_REF (use_stmt_info)
> + && DR_IS_WRITE (STMT_VINFO_DATA_REF (use_stmt_info)));
> +}
> +
> /* Return true if STMT_INFO is an integer truncation. */
> inline bool
> vect_is_integer_truncation (stmt_vec_info stmt_info)
> --
> 2.54.0