On Wed, Jun 3, 2026 at 12:04 PM 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.
>
>   Fold this idiom to IFN_MUL_OVERFLOW, matching the existing canonical form 
> used for other unsigned multiplication overflow checks.
>
>   gcc/
>         * match.pd: 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]>
> ---
>  gcc/match.pd                                  | 22 +++++++
>  .../g++.target/i386/mul-overflow-widen-1.C    | 64 +++++++++++++++++++
>  .../g++.target/i386/mul-overflow-widen-2.C    | 36 +++++++++++
>  3 files changed, 122 insertions(+)
>  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..5fad200f67b 100644
> --- a/gcc/match.pd
> +++ b/gcc/match.pd
> @@ -8478,6 +8478,28 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
>     (with { tree t = TREE_TYPE (@0), cpx = build_complex_type (t); }
>      (cmp (imagpart (IFN_MUL_OVERFLOW:cpx @0 @1)) { build_zero_cst (t); })))))
>
> +/* Likewise, ((type) A * B) > max where type is at least twice as wide
> +   as the type of unsigned A and B checks whether A * B would overflow.  */
> +(for cmp (gt le)
> +     out (ne eq)
> + (simplify
> +  (cmp:c (mult:s (convert@3 @0) (convert @1)) INTEGER_CST@2)
The :c on the compare should not be necessary.

Given the

/* 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>).  */
(for cmp (eq ne)
 (simplify
  (cmp (rshift (mult:s (convert@3 @0) (convert @1)) INTEGER_CST@2)
       integer_zerop)

pattern I wonder if either variant should be the canonical form even when
there's no support for umulv4?   Thus, we'd have a canonicalization patttern
and only one pattern recognizing IFN_MUL_OVERFLOW?  The variant
without the shift is one operation less, so I'd declare that canonical?

> +  (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); }
> +    (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)
>   (simplify
> 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