On Tue, Sep 29, 2026 at 11:52 PM Dominic P <[email protected]> wrote:
>
> can_min_p compares POLY_INT_CSTs as poly_widest_ints, which zero-extends
> the coefficients of an unsigned type. 33 - svcntb () in uint64_t is
> POLY_INT_CST [17, 18446744073709551600] (17 - 16x), which then looks
> known to be >= svcntb (), so MIN (33 - svcntb (), svcntb ()) folds to
> svcntb () although the result is 1 at VL 256. Punt when an unsigned
> POLY_INT_CST operand may be negative once its coefficients are
> sign-extended. GCC 16 is affected too. RISC-V V uses the same folding;
> no rvv test changes. Values that wrap past the top of the type, such as
> (uint8_t) svcntb () at VL 2048, are a separate issue.
>
> Bootstrapped and regression tested on x86_64-pc-linux-gnu; regression
> tested on aarch64-linux-gnu (qemu, VL 128 and SVE VL 2048).
>
> Assisted-by: Claude Opus 5 (Anthropic)
>
> gcc/ChangeLog:
>
> * fold-const.cc (can_min_p): Punt on unsigned POLY_INT_CST operands
> that may be negative when read as signed.
>
> gcc/testsuite/ChangeLog:
>
> * gcc.target/aarch64/sve/acle/general/min_poly_1.c: New test.
>
> Signed-off-by: Dominic P <[email protected]>
> ---
> OK for trunk?
>
> gcc/fold-const.cc | 8 +++++
> .../aarch64/sve/acle/general/min_poly_1.c | 33 +++++++++++++++++++
> 2 files changed, 41 insertions(+)
> create mode 100644
> gcc/testsuite/gcc.target/aarch64/sve/acle/general/min_poly_1.c
>
> diff --git a/gcc/fold-const.cc b/gcc/fold-const.cc
> index 113558973f6..c963a29b265 100644
> --- a/gcc/fold-const.cc
> +++ b/gcc/fold-const.cc
> @@ -1149,6 +1149,14 @@ wide_int_binop (wide_int &res,
> bool
> can_min_p (const_tree arg1, const_tree arg2, poly_wide_int &res)
> {
> + /* An unsigned POLY_INT_CST with a coefficient that is negative when
> + read as signed can wrap. */
> + for (const_tree arg : { arg1, arg2 })
> + if (POLY_INT_CST_P (arg)
> + && TYPE_UNSIGNED (TREE_TYPE (arg))
> + && maybe_lt (tree_to_poly_int64 (arg), 0))
> + return false;
Since it is only 3 lines, either extract this out into its own
function (even a lambda) or just do it inline with arg1 and arg2
directly. I am not a fan of using "range for" with just 2 elements
like this.
> +
> if (known_le (wi::to_poly_widest (arg1), wi::to_poly_widest (arg2)))
> {
> res = wi::to_poly_wide (arg1);
> diff --git a/gcc/testsuite/gcc.target/aarch64/sve/acle/general/min_poly_1.c
> b/gcc/testsuite/gcc.target/aarch64/sve/acle/general/min_poly_1.c
> new file mode 100644
> index 00000000000..99a7139e9f1
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/sve/acle/general/min_poly_1.c
> @@ -0,0 +1,33 @@
> +/* { dg-do run { target aarch64_sve_hw } } */
> +/* { dg-options "-O2" } */
> +
> +#include <arm_sve.h>
> +
> +#define TEST(NAME, T, K, CNT) \
> + T __attribute__ ((noipa)) \
> + NAME (void) \
> + { \
> + T n = (T) K - (T) CNT (); \
> + return n < (T) CNT () ? n : (T) CNT (); \
> + }
> +
> +TEST (f1, uint64_t, 33, svcntb)
> +TEST (f2, uint32_t, 33, svcnth)
> +TEST (f3, uint64_t, 130, svcntb)
> +TEST (f4, uint64_t, 300, svcntb)
> +
> +int __attribute__ ((optimize (0)))
> +main (void)
> +{
> +#define CHECK(NAME, T, K, CNT) \
> + { \
> + T c = CNT (), n = (T) K - c; \
> + if (NAME () != (n < c ? n : c)) \
> + __builtin_abort (); \
> + }
> + CHECK (f1, uint64_t, 33, svcntb)
> + CHECK (f2, uint32_t, 33, svcnth)
> + CHECK (f3, uint64_t, 130, svcntb)
> + CHECK (f4, uint64_t, 300, svcntb)
> + return 0;
> +}
> --
> 2.55.0
>