On Thu, Jun 4, 2026 at 5:31 AM Xuhang Zhou <[email protected]> wrote:
>
> From: Zhou Xuhang <[email protected]>
>
> For unsigned operands, a widened multiplication compared against the maximum 
> value of the narrow type is an overflow check when the widened type is at 
> least twice as precise as the operand type.
>
> Canonicalize the shifted high-half form to the widened comparison form, then 
> fold the comparison form to IFN_MUL_OVERFLOW when the target has direct 
> unsigned multiply-overflow support.

This is OK.

Thanks and sorry for the delay.
Richard.

>   gcc/
>         * match.pd: Canonicalize shifted high-half unsigned multiply
>         overflow checks to widened comparisons.  Simplify widened unsigned
>         multiply comparison against the narrow type maximum to 
> IFN_MUL_OVERFLOW.
>
>   gcc/testsuite/
>         * g++.target/i386/mul-overflow-widen-1.C: New test.
>         * g++.target/i386/mul-overflow-widen-2.C: New test.
>
> Signed-off-by: Zhou Xuhang <[email protected]>
> ---
> Thanks for your review! I agree with your idea and updated the patch along 
> those lines.
>
> Changes in v2:
>   - Removed the unnecessary :c from the comparison pattern.
>   - Changed the shifted high-half idiom to canonicalize to the following 
> widened comparison form.
>   - Kept the umulv4 optab check only on the final widened-comparison to 
> IFN_MUL_OVERFLOW fold.
>   - Added uint16_t and uint64_t/__int128 test coverage.
>
> I also tested the no-umulv4 path by temporarily disabling the final 
> umulv4-gated fold. In that setup, the shifted form canonicalized to the 
> widened comparison form as expected.
>
>
>  gcc/match.pd                                  | 31 +++++++--
>  .../g++.target/i386/mul-overflow-widen-1.C    | 64 +++++++++++++++++++
>  .../g++.target/i386/mul-overflow-widen-2.C    | 36 +++++++++++
>  3 files changed, 127 insertions(+), 4 deletions(-)
>  create mode 100644 gcc/testsuite/g++.target/i386/mul-overflow-widen-1.C
>  create mode 100644 gcc/testsuite/g++.target/i386/mul-overflow-widen-2.C
>
> diff --git a/gcc/match.pd b/gcc/match.pd
> index 0be4eff818b..b9fcda03d4b 100644
> --- a/gcc/match.pd
> +++ b/gcc/match.pd
> @@ -8457,10 +8457,11 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
>     (with { tree t = TREE_TYPE (@0), cpx = build_complex_type (t); }
>      (out (imagpart (IFN_MUL_OVERFLOW:cpx @0 @1)) { build_zero_cst (t); })))))
>
> -/* Similarly, for unsigned operands, (((type) A * B) >> prec) != 0 where type
> -   is at least twice as wide as type of A and B, simplify to
> -   __builtin_mul_overflow (A, B, <unused>).  */
> +/* Similarly, for unsigned operands, (((type) A * B) >> prec) != 0
> +   (or == 0) where type is at least twice as wide as type of A and B,
> +   canonicalize to the following ((utype) A * B) > max (or <= max) pattern.  
> */
>  (for cmp (eq ne)
> +     out (le gt)
>   (simplify
>    (cmp (rshift (mult:s (convert@3 @0) (convert @1)) INTEGER_CST@2)
>         integer_zerop)
> @@ -8472,11 +8473,33 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
>         && tree_fits_uhwi_p (@2)
>         && tree_to_uhwi (@2) == TYPE_PRECISION (TREE_TYPE (@0))
>         && types_match (@0, @1)
> +       && TYPE_MAX_VALUE (TREE_TYPE (@0)))
> +   (with { tree utype = unsigned_type_for (TREE_TYPE (@3)); }
> +    (out (mult:utype (convert:utype @0) (convert:utype @1))
> +        { fold_convert (utype, TYPE_MAX_VALUE (TREE_TYPE (@0))); })))))
> +
> +/* For unsigned operands, ((type) A * B) > max where type is at least twice 
> as wide
> +   as the type of unsigned A and B.
> +   Simplify it to __builtin_mul_overflow (A, B, <unused>). */
> +(for cmp (gt le)
> +     out (ne eq)
> + (simplify
> +  (cmp (mult:s (convert@3 @0) (convert @1)) INTEGER_CST@2)
> +  (if (INTEGRAL_TYPE_P (TREE_TYPE (@0))
> +       && INTEGRAL_TYPE_P (TREE_TYPE (@3))
> +       && TYPE_UNSIGNED (TREE_TYPE (@0))
> +       && TYPE_UNSIGNED (TREE_TYPE (@3))
> +       && (TYPE_PRECISION (TREE_TYPE (@3))
> +          >= 2 * TYPE_PRECISION (TREE_TYPE (@0)))
> +       && TYPE_MAX_VALUE (TREE_TYPE (@0))
> +       && (wi::to_widest (@2)
> +          == wi::to_widest (TYPE_MAX_VALUE (TREE_TYPE (@0))))
> +       && types_match (@0, @1)
>         && type_has_mode_precision_p (TREE_TYPE (@0))
>         && (optab_handler (umulv4_optab, TYPE_MODE (TREE_TYPE (@0)))
>            != CODE_FOR_nothing))
>     (with { tree t = TREE_TYPE (@0), cpx = build_complex_type (t); }
> -    (cmp (imagpart (IFN_MUL_OVERFLOW:cpx @0 @1)) { build_zero_cst (t); })))))
> +    (out (imagpart (IFN_MUL_OVERFLOW:cpx @0 @1)) { build_zero_cst (t); })))))
>
>  /* Demote operands of IFN_{ADD,SUB,MUL}_OVERFLOW.  */
>  (for ovf (IFN_ADD_OVERFLOW IFN_SUB_OVERFLOW IFN_MUL_OVERFLOW)
> diff --git a/gcc/testsuite/g++.target/i386/mul-overflow-widen-1.C 
> b/gcc/testsuite/g++.target/i386/mul-overflow-widen-1.C
> new file mode 100644
> index 00000000000..d1002717a13
> --- /dev/null
> +++ b/gcc/testsuite/g++.target/i386/mul-overflow-widen-1.C
> @@ -0,0 +1,64 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fdump-tree-optimized -masm=att" } */
> +/* { dg-final { scan-tree-dump-times " = \\.MUL_OVERFLOW " 8 "optimized" } } 
> */
> +/* { dg-final { scan-assembler-times "\tmulw\t" 3 } } */
> +/* { dg-final { scan-assembler-times "\tmull\t" 5 } } */
> +/* { dg-final { scan-assembler-times "\tseto\t" 5 } } */
> +/* { dg-final { scan-assembler-times "\tsetno\t" 3 } } */
> +
> +typedef unsigned short uint16_t;
> +typedef unsigned int uint32_t;
> +typedef unsigned long long uint64_t;
> +
> +#define UINT16_MAX 65535U
> +#define UINT32_MAX 4294967295U
> +
> +bool
> +mul16_gt (uint16_t x, uint16_t y)
> +{
> +  return uint32_t (x) * y > UINT16_MAX;
> +}
> +
> +bool
> +mul16_le (uint16_t x, uint16_t y)
> +{
> +  return uint32_t (x) * y <= UINT16_MAX;
> +}
> +
> +bool
> +mul16_builtin (uint16_t x, uint16_t y)
> +{
> +  uint16_t result;
> +  return __builtin_mul_overflow (x, y, &result);
> +}
> +
> +bool
> +mul32_gt (uint32_t x, uint32_t y)
> +{
> +  return uint64_t (x) * y > UINT32_MAX;
> +}
> +
> +bool
> +mul32_le (uint32_t x, uint32_t y)
> +{
> +  return uint64_t (x) * y <= UINT32_MAX;
> +}
> +
> +bool
> +mul32_gt_rev (uint32_t x, uint32_t y)
> +{
> +  return UINT32_MAX < uint64_t (x) * y;
> +}
> +
> +bool
> +mul32_le_rev (uint32_t x, uint32_t y)
> +{
> +  return UINT32_MAX >= uint64_t (x) * y;
> +}
> +
> +bool
> +mul32_builtin (uint32_t x, uint32_t y)
> +{
> +  uint32_t result;
> +  return __builtin_mul_overflow (x, y, &result);
> +}
> diff --git a/gcc/testsuite/g++.target/i386/mul-overflow-widen-2.C 
> b/gcc/testsuite/g++.target/i386/mul-overflow-widen-2.C
> new file mode 100644
> index 00000000000..94f5eb9de30
> --- /dev/null
> +++ b/gcc/testsuite/g++.target/i386/mul-overflow-widen-2.C
> @@ -0,0 +1,36 @@
> +/* { dg-do compile { target int128 } } */
> +/* { dg-options "-O2 -fdump-tree-optimized -masm=att" } */
> +/* { dg-final { scan-tree-dump-times " = \\.MUL_OVERFLOW " 4 "optimized" } } 
> */
> +/* { dg-final { scan-assembler-times "\tmulq\t" 4 } } */
> +/* { dg-final { scan-assembler-times "\tseto\t" 3 } } */
> +/* { dg-final { scan-assembler-times "\tsetno\t" 1 } } */
> +
> +typedef unsigned long long uint64_t;
> +typedef unsigned __int128 uint128_t;
> +
> +#define UINT64_MAX 18446744073709551615ULL
> +
> +bool
> +mul64_gt (uint64_t x, uint64_t y)
> +{
> +  return uint128_t (x) * y > UINT64_MAX;
> +}
> +
> +bool
> +mul64_le (uint64_t x, uint64_t y)
> +{
> +  return uint128_t (x) * y <= UINT64_MAX;
> +}
> +
> +bool
> +mul64_high (uint64_t x, uint64_t y)
> +{
> +  return ((__int128) x * y >> 64) != 0;
> +}
> +
> +bool
> +mul64_builtin (uint64_t x, uint64_t y)
> +{
> +  uint64_t result;
> +  return __builtin_mul_overflow (x, y, &result);
> +}
> --
> 2.43.0
>

Reply via email to