When expanding __builtin_mul_overflow for an unsigned _BitInt result
whose precision is smaller than its machine mode, the widening-multiply
path currently checks overflow at the mode precision and then performs
a second fit check when storing the result.

Use the result type's precision for the overflow check, convert the
product before branching and inform the result-store helper that
overflow has already been checked. The result is still normalized to
the declared precision, while the redundant fit check is omitted.

Regression Tested on X64-64 and aarch64 with no new failures.

gcc/ChangeLog:
        PR middle-end/116135
        * internal-fn.cc (expand_arith_overflow_result_store): Add
        overflow_checked argument. Skip the redundant fit check when
        overflow has already been checked but normalize the result.
        (expand_addsub_overflow, expand_neg_overflow): Update callers.
        (expand_mul_overflow): Check unsigned multiplication overflow at
        the result precision for sub-mode precision results.

gcc/testsuite/ChangeLog:
        PR middle-end/116135
        * gcc.dg/torture/pr116135.c: New test.
        * gcc.target/aarch64/pr116135.c: New test.

Signed-off-by: Naveen <[email protected]>
---
 gcc/internal-fn.cc                          | 74 +++++++++++++++++----
 gcc/testsuite/gcc.dg/torture/pr116135.c     | 44 ++++++++++++
 gcc/testsuite/gcc.target/aarch64/pr116135.c | 14 ++++
 3 files changed, 118 insertions(+), 14 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/torture/pr116135.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/pr116135.c

diff --git a/gcc/internal-fn.cc b/gcc/internal-fn.cc
index 45ad08d2cf6..1aea0accda4 100644
--- a/gcc/internal-fn.cc
+++ b/gcc/internal-fn.cc
@@ -1073,11 +1073,14 @@ expand_arith_set_overflow (tree lhs, rtx target)
 /* Helper for expand_*_overflow.  Store RES into the __real__ part
    of TARGET.  If RES has larger MODE than __real__ part of TARGET,
    set the __imag__ part to 1 if RES doesn't fit into it.  Similarly
-   if LHS has smaller precision than its mode.  */
+   if LHS has smaller precision than its mode, unless OVERFLOW_CHECKED
+   is true in which case the caller has already performed the
+   overflow check and recorded the result.  */
 
 static void
 expand_arith_overflow_result_store (tree lhs, rtx target,
-                                   scalar_int_mode mode, rtx res)
+                                   scalar_int_mode mode, rtx res,
+                                   bool overflow_checked = false)
 {
   scalar_int_mode tgtmode
     = as_a <scalar_int_mode> (GET_MODE_INNER (GET_MODE (target)));
@@ -1096,7 +1099,7 @@ expand_arith_overflow_result_store (tree lhs, rtx target,
     }
   int prec = TYPE_PRECISION (TREE_TYPE (TREE_TYPE (lhs)));
   int tgtprec = GET_MODE_PRECISION (tgtmode);
-  if (prec < tgtprec)
+  if (prec < tgtprec && !overflow_checked)
     {
       rtx_code_label *done_label = gen_label_rtx ();
       int uns = TYPE_UNSIGNED (TREE_TYPE (TREE_TYPE (lhs)));
@@ -1122,6 +1125,28 @@ expand_arith_overflow_result_store (tree lhs, rtx target,
       expand_arith_set_overflow (lhs, target);
       emit_label (done_label);
     }
+  else if (prec < tgtprec)
+    {
+      /* overflow_checked: the caller already detected and recorded overflow
+        via the hipart comparison.  */
+      int uns = TYPE_UNSIGNED (TREE_TYPE (TREE_TYPE (lhs)));
+      res = lres;
+      if (uns)
+       {
+         rtx mask
+           = immed_wide_int_const (wi::shifted_mask (0, prec, false, tgtprec),
+                                   tgtmode);
+         lres = expand_simple_binop (tgtmode, AND, res, mask, NULL_RTX,
+                                     true, OPTAB_LIB_WIDEN);
+       }
+      else
+       {
+         lres = expand_shift (LSHIFT_EXPR, tgtmode, res, tgtprec - prec,
+                              NULL_RTX, 1);
+         lres = expand_shift (RSHIFT_EXPR, tgtmode, lres, tgtprec - prec,
+                              NULL_RTX, 0);
+       }
+    }
   write_complex_part (target, lres, false, false);
 }
 
@@ -1617,7 +1642,7 @@ expand_addsub_overflow (location_t loc, tree_code code, 
tree lhs,
            res = expand_binop (mode, add_optab, res, sgn, NULL_RTX, false,
                                OPTAB_LIB_WIDEN);
 
-         expand_arith_overflow_result_store (lhs, target, mode, res);
+         expand_arith_overflow_result_store (lhs, target, mode, res, false);
        }
     }
 }
@@ -1709,7 +1734,7 @@ expand_neg_overflow (location_t loc, tree lhs, tree arg1, 
bool is_ubsan,
       if (is_ubsan)
        expand_ubsan_result_store (lhs, target, mode, res, do_error);
       else
-       expand_arith_overflow_result_store (lhs, target, mode, res);
+       expand_arith_overflow_result_store (lhs, target, mode, res, false);
     }
 }
 
@@ -1785,6 +1810,7 @@ expand_mul_overflow (location_t loc, tree lhs, tree arg0, 
tree arg1,
   op1 = expand_normal (arg1);
 
   bool uns = unsr_p;
+  bool overflow_checked = false;
   if (lhs)
     {
       target = expand_expr (lhs, NULL_RTX, VOIDmode, EXPAND_WRITE);
@@ -2197,23 +2223,42 @@ expand_mul_overflow (location_t loc, tree lhs, tree 
arg0, tree arg1,
          && can_widen_mult_without_libcall (wmode, mode, op0, op1, uns))
        {
        twoxwider:
+         int ovf_prec = prec;
+         if (!is_ubsan && lhs && unsr_p)
+           ovf_prec = TYPE_PRECISION (TREE_TYPE (TREE_TYPE (lhs)));
          ops.code = WIDEN_MULT_EXPR;
          ops.type
            = build_nonstandard_integer_type (GET_MODE_PRECISION (wmode), uns);
 
          res = expand_expr_real_2 (&ops, NULL_RTX, wmode, EXPAND_NORMAL);
-         rtx hipart = expand_shift (RSHIFT_EXPR, wmode, res, prec,
+         rtx hipart = expand_shift (RSHIFT_EXPR, wmode, res, ovf_prec,
                                     NULL_RTX, uns);
-         hipart = convert_modes (mode, wmode, hipart, uns);
-         res = convert_modes (mode, wmode, res, uns);
          if (uns)
-           /* For the unsigned multiplication, there was overflow if
-              HIPART is non-zero.  */
-           do_compare_rtx_and_jump (hipart, const0_rtx, EQ, true, mode,
-                                    NULL_RTX, NULL, done_label,
-                                    profile_probability::very_likely ());
+           {
+             /* For the unsigned multiplication, there was overflow if
+                HIPART is non-zero. Convert RES before the branch so that
+                both the no-overflow fall-through and the overflow path see
+                RES in MODE.  */
+             res = convert_modes (mode, wmode, res, uns);
+             if (ovf_prec < prec)
+               {
+                 overflow_checked = true;
+                 do_compare_rtx_and_jump (hipart, const0_rtx, EQ, true, wmode,
+                                          NULL_RTX, NULL, done_label,
+                                          profile_probability::very_likely ());
+               }
+             else
+               {
+                 hipart = convert_modes (mode, wmode, hipart, uns);
+                 do_compare_rtx_and_jump (hipart, const0_rtx, EQ, true, mode,
+                                          NULL_RTX, NULL, done_label,
+                                          profile_probability::very_likely ());
+               }
+           }
          else
            {
+             hipart = convert_modes (mode, wmode, hipart, uns);
+             res = convert_modes (mode, wmode, res, uns);
              /* RES is used more than once, place it in a pseudo.  */
              res = force_reg (mode, res);
 
@@ -2615,7 +2660,8 @@ expand_mul_overflow (location_t loc, tree lhs, tree arg0, 
tree arg1,
       if (is_ubsan)
        expand_ubsan_result_store (lhs, target, mode, res, do_error);
       else
-       expand_arith_overflow_result_store (lhs, target, mode, res);
+       expand_arith_overflow_result_store (lhs, target, mode, res,
+                                           overflow_checked);
     }
   flag_trapv = save_flag_trapv;
 }
diff --git a/gcc/testsuite/gcc.dg/torture/pr116135.c 
b/gcc/testsuite/gcc.dg/torture/pr116135.c
new file mode 100644
index 00000000000..8309e552ffa
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/pr116135.c
@@ -0,0 +1,44 @@
+/* PR middle-end/116135 */
+/* { dg-do run { target bitint } } */
+/* { dg-options "-std=c23" } */
+
+__attribute__((noipa)) int
+mul31 (unsigned _BitInt(31) x, unsigned _BitInt(31) y,
+       unsigned _BitInt(31) *res)
+{
+  return __builtin_mul_overflow (x, y, res);
+}
+
+__attribute__((noipa)) int
+mul31_wide (unsigned long long x, unsigned long long y,
+            unsigned _BitInt(31) *res)
+{
+  return __builtin_mul_overflow (x, y, res);
+}
+
+int
+main ()
+{
+  unsigned _BitInt(31) res;
+
+  if (!mul31 (((unsigned _BitInt(31)) 1) << 30, 2, &res) || res != 0)
+    __builtin_abort ();
+
+  if (mul31 (((unsigned _BitInt(31)) 1) << 15,
+            (((unsigned _BitInt(31)) 1) << 15) - 1, &res)
+      || res != ((((unsigned _BitInt(31)) 1) << 30)
+                - (((unsigned _BitInt(31)) 1) << 15)))
+    __builtin_abort ();
+
+  if (!mul31_wide (1ULL << 63, 1ULL << 63, &res) || res != 0)
+    __builtin_abort ();
+
+  if (!mul31_wide (1ULL << 31, 1, &res) || res != 0)
+    __builtin_abort ();
+
+  if (mul31_wide (1234, 5678, &res)
+      || res != (unsigned _BitInt(31)) (1234ULL * 5678ULL))
+    __builtin_abort ();
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/aarch64/pr116135.c 
b/gcc/testsuite/gcc.target/aarch64/pr116135.c
new file mode 100644
index 00000000000..398a583c5ac
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/pr116135.c
@@ -0,0 +1,14 @@
+/* PR middle-end/116135 */
+/* { dg-do compile } */
+/* { dg-require-effective-target bitint } */
+/* { dg-options "-O2 -std=c23" } */
+
+int
+foo (unsigned _BitInt(31) x, unsigned _BitInt(31) y,
+     unsigned _BitInt(31) *res)
+{
+  return __builtin_mul_overflow (x, y, res);
+}
+
+/* { dg-final { scan-assembler-times {\tcmp\txzr, x[0-9]+, lsr 31} 1 } } */
+/* { dg-final { scan-assembler-not {\tcmp\txzr, x[0-9]+, lsr 32} } } */
-- 
2.34.1

Reply via email to