>From 3f062e535fb22ba95de195357364ec503cb28ceb Mon Sep 17 00:00:00 2001
From: Uttam Singh Bhadauriya <[email protected]>
Date: Wed, 21 Oct 2026 10:23:10 +0530
Subject: [PATCH v2] match.pd: Recognize X == MIN ? MIN : X - 1 as signed sat add
[PR127985]
Commit ff133b4cd22 (PR126627) simplified (X & -X) < 0 into X == INT_MIN
and (X & -X) >= 0 into X != INT_MIN. However, the signed saturating
addition pattern for IMM = -1 in match-sat-alu.pd looked for the
unsimplified (X & -X) < 0 condition. As a result, a saturating decrement
is no longer recognized as .SAT_ADD, which causes regressions in
gcc.target/riscv/sat/sat_s_add_imm-*.c.
Because the old (X & -X) < 0 pattern can no longer match outside
-ftrapv, this patch replaces it with match patterns for the canonical
X == MIN ? MIN : X - 1 and X != MIN ? X - 1 : MIN forms. This restores
.SAT_ADD discovery on RISC-V and also captures saturating decrements
written directly with MIN comparisons.
PR tree-optimization/127985
gcc/ChangeLog:
* match-sat-alu.pd (signed_integer_sat_add): Replace obsolete
(X & -X) < 0 pattern with patterns for X == MIN ? MIN : X - 1
and X != MIN ? X - 1 : MIN.
gcc/testsuite/ChangeLog:
* gcc.dg/torture/pr127985.c: New test.
* gcc.target/riscv/sat/pr127985.c: New test.
Signed-off-by: Uttam Singh Bhadauriya <[email protected]>
---
Hello,
Following the suggestions by Zhongyao, I have updated the patch to
recognize both
comparison polarities:
X == MIN ? MIN : X - 1
X != MIN ? X - 1 : MIN
Because commit ff133b4cd22 (PR126627) eagerly simplifies (X & -X) < 0 into
X == INT_MIN and (X & -X) >= 0 into X != INT_MIN before math-opts runs,
the old (X & -X) < 0 pattern in match-sat-alu.pd can no longer match under
normal optimization. This patch cleanly replaces it with the two canonical
forms above.
The patch includes:
- A target-independent dg-do run torture test covering MIN, MIN + 1, -1, 0, 10,
and MAX across 8, 16, 32, and 64-bit signed types.
- A RISC-V scan test verifying 8 .SAT_ADD occurrences and branchless assembly.
Regtested on x86_64-pc-linux-gnu:
- dg-torture: pr127985.c passed execution across -O0, -O1, -O2, -O3, and -Os.
Requesting you to re-run the rv64gcv tests on this updated patch and
confirm if everything works fine.
Kind regards,
Uttam
gcc/match-sat-alu.pd | 31 ++++-
gcc/testsuite/gcc.dg/torture/pr127985.c | 122 ++++++++++++++++++
gcc/testsuite/gcc.target/riscv/sat/pr127985.c | 56 ++++++++
3 files changed, 204 insertions(+), 5 deletions(-)
create mode 100644 gcc/testsuite/gcc.dg/torture/pr127985.c
create mode 100644 gcc/testsuite/gcc.target/riscv/sat/pr127985.c
diff --git a/gcc/match-sat-alu.pd b/gcc/match-sat-alu.pd
index 236d6fb2e19..28bed437334 100644
--- a/gcc/match-sat-alu.pd
+++ b/gcc/match-sat-alu.pd
@@ -486,10 +486,8 @@ along with GCC; see the file COPYING3. If not see
(match (signed_integer_sat_add @0 @1)
/* T SUM = (T)((UT)X + (UT)-1);
- SAT_S_ADD = (X ^ -1) < 0 ? SUM : (X ^ SUM) >= 0 ? SUM
- : (x < 0) ? MIN : MAX */
- (convert (cond^ (lt (bit_and:c @0 (nop_convert (negate (nop_convert @0))))
- integer_zerop)
+ SAT_S_ADD = X == MIN ? MIN : SUM. */
+ (convert (cond^ (eq (nop_convert? @0) INTEGER_CST@3)
INTEGER_CST@2
(plus (nop_convert @0) integer_all_onesp@1)))
(with
@@ -497,11 +495,34 @@ along with GCC; see the file COPYING3. If not see
unsigned precision = TYPE_PRECISION (type);
wide_int c1 = wi::to_wide (@1);
wide_int c2 = wi::to_wide (@2);
+ wide_int c3 = wi::to_wide (@3);
+ wide_int sum = wi::add (c1, c2);
+ }
+ /* The outer conversion must not change the type of X. */
+ (if (types_match (type, @0)
+ && TYPE_PRECISION (TREE_TYPE (@3)) == precision
+ && wi::eq_p (sum, wi::max_value (precision, SIGNED))
+ && wi::eq_p (c3, wi::min_value (precision, SIGNED))))))
+
+(match (signed_integer_sat_add @0 @1)
+ /* T SUM = (T)((UT)X + (UT)-1);
+ SAT_S_ADD = X != MIN ? SUM : MIN. */
+ (convert (cond^ (ne (nop_convert? @0) INTEGER_CST@3)
+ (plus (nop_convert @0) integer_all_onesp@1)
+ INTEGER_CST@2))
+ (with
+ {
+ unsigned precision = TYPE_PRECISION (type);
+ wide_int c1 = wi::to_wide (@1);
+ wide_int c2 = wi::to_wide (@2);
+ wide_int c3 = wi::to_wide (@3);
wide_int sum = wi::add (c1, c2);
}
/* The outer conversion must not change the type of X. */
(if (types_match (type, @0)
- && wi::eq_p (sum, wi::max_value (precision, SIGNED))))))
+ && TYPE_PRECISION (TREE_TYPE (@3)) == precision
+ && wi::eq_p (sum, wi::max_value (precision, SIGNED))
+ && wi::eq_p (c3, wi::min_value (precision, SIGNED))))))
(match (signed_integer_sat_add @0 @1)
/* T SUM = (T)((UT)X + (UT)IMM)
diff --git a/gcc/testsuite/gcc.dg/torture/pr127985.c
b/gcc/testsuite/gcc.dg/torture/pr127985.c
new file mode 100644
index 00000000000..0ebdc47b2b4
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/pr127985.c
@@ -0,0 +1,122 @@
+/* PR tree-optimization/127985 */
+/* { dg-do run } */
+
+#include <stdint.h>
+
+__attribute__((noipa)) int8_t
+dec_i8 (int8_t x)
+{
+ return x == INT8_MIN ? INT8_MIN : x - 1;
+}
+
+__attribute__((noipa)) int16_t
+dec_i16 (int16_t x)
+{
+ return x == INT16_MIN ? INT16_MIN : x - 1;
+}
+
+__attribute__((noipa)) int32_t
+dec_i32 (int32_t x)
+{
+ return x == INT32_MIN ? INT32_MIN : x - 1;
+}
+
+__attribute__((noipa)) int64_t
+dec_i64 (int64_t x)
+{
+ return x == INT64_MIN ? INT64_MIN : x - 1;
+}
+
+__attribute__((noipa)) int8_t
+dec_ne_i8 (int8_t x)
+{
+ return x != INT8_MIN ? x - 1 : INT8_MIN;
+}
+
+__attribute__((noipa)) int16_t
+dec_ne_i16 (int16_t x)
+{
+ return x != INT16_MIN ? x - 1 : INT16_MIN;
+}
+
+__attribute__((noipa)) int32_t
+dec_ne_i32 (int32_t x)
+{
+ return x != INT32_MIN ? x - 1 : INT32_MIN;
+}
+
+__attribute__((noipa)) int64_t
+dec_ne_i64 (int64_t x)
+{
+ return x != INT64_MIN ? x - 1 : INT64_MIN;
+}
+
+int
+main (void)
+{
+ if (dec_i8 (INT8_MIN) != INT8_MIN
+ || dec_i8 (INT8_MIN + 1) != INT8_MIN
+ || dec_i8 (-1) != -2
+ || dec_i8 (0) != -1
+ || dec_i8 (10) != 9
+ || dec_i8 (INT8_MAX) != INT8_MAX - 1)
+ __builtin_abort ();
+
+ if (dec_i16 (INT16_MIN) != INT16_MIN
+ || dec_i16 (INT16_MIN + 1) != INT16_MIN
+ || dec_i16 (-1) != -2
+ || dec_i16 (0) != -1
+ || dec_i16 (10) != 9
+ || dec_i16 (INT16_MAX) != INT16_MAX - 1)
+ __builtin_abort ();
+
+ if (dec_i32 (INT32_MIN) != INT32_MIN
+ || dec_i32 (INT32_MIN + 1) != INT32_MIN
+ || dec_i32 (-1) != -2
+ || dec_i32 (0) != -1
+ || dec_i32 (10) != 9
+ || dec_i32 (INT32_MAX) != INT32_MAX - 1)
+ __builtin_abort ();
+
+ if (dec_i64 (INT64_MIN) != INT64_MIN
+ || dec_i64 (INT64_MIN + 1) != INT64_MIN
+ || dec_i64 (-1) != -2
+ || dec_i64 (0) != -1
+ || dec_i64 (10) != 9
+ || dec_i64 (INT64_MAX) != INT64_MAX - 1)
+ __builtin_abort ();
+
+ if (dec_ne_i8 (INT8_MIN) != INT8_MIN
+ || dec_ne_i8 (INT8_MIN + 1) != INT8_MIN
+ || dec_ne_i8 (-1) != -2
+ || dec_ne_i8 (0) != -1
+ || dec_ne_i8 (10) != 9
+ || dec_ne_i8 (INT8_MAX) != INT8_MAX - 1)
+ __builtin_abort ();
+
+ if (dec_ne_i16 (INT16_MIN) != INT16_MIN
+ || dec_ne_i16 (INT16_MIN + 1) != INT16_MIN
+ || dec_ne_i16 (-1) != -2
+ || dec_ne_i16 (0) != -1
+ || dec_ne_i16 (10) != 9
+ || dec_ne_i16 (INT16_MAX) != INT16_MAX - 1)
+ __builtin_abort ();
+
+ if (dec_ne_i32 (INT32_MIN) != INT32_MIN
+ || dec_ne_i32 (INT32_MIN + 1) != INT32_MIN
+ || dec_ne_i32 (-1) != -2
+ || dec_ne_i32 (0) != -1
+ || dec_ne_i32 (10) != 9
+ || dec_ne_i32 (INT32_MAX) != INT32_MAX - 1)
+ __builtin_abort ();
+
+ if (dec_ne_i64 (INT64_MIN) != INT64_MIN
+ || dec_ne_i64 (INT64_MIN + 1) != INT64_MIN
+ || dec_ne_i64 (-1) != -2
+ || dec_ne_i64 (0) != -1
+ || dec_ne_i64 (10) != 9
+ || dec_ne_i64 (INT64_MAX) != INT64_MAX - 1)
+ __builtin_abort ();
+
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/riscv/sat/pr127985.c
b/gcc/testsuite/gcc.target/riscv/sat/pr127985.c
new file mode 100644
index 00000000000..50c61def96b
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/sat/pr127985.c
@@ -0,0 +1,56 @@
+/* PR tree-optimization/127985 */
+/* { dg-do compile } */
+/* { dg-options "-march=rv64gc -mabi=lp64d -O3 -fdump-tree-optimized" } */
+
+#include <stdint.h>
+
+int8_t
+dec_i8 (int8_t x)
+{
+ return x == INT8_MIN ? INT8_MIN : x - 1;
+}
+
+int16_t
+dec_i16 (int16_t x)
+{
+ return x == INT16_MIN ? INT16_MIN : x - 1;
+}
+
+int32_t
+dec_i32 (int32_t x)
+{
+ return x == INT32_MIN ? INT32_MIN : x - 1;
+}
+
+int64_t
+dec_i64 (int64_t x)
+{
+ return x == INT64_MIN ? INT64_MIN : x - 1;
+}
+
+int8_t
+dec_ne_i8 (int8_t x)
+{
+ return x != INT8_MIN ? x - 1 : INT8_MIN;
+}
+
+int16_t
+dec_ne_i16 (int16_t x)
+{
+ return x != INT16_MIN ? x - 1 : INT16_MIN;
+}
+
+int32_t
+dec_ne_i32 (int32_t x)
+{
+ return x != INT32_MIN ? x - 1 : INT32_MIN;
+}
+
+int64_t
+dec_ne_i64 (int64_t x)
+{
+ return x != INT64_MIN ? x - 1 : INT64_MIN;
+}
+
+/* { dg-final { scan-tree-dump-times ".SAT_ADD " 8 "optimized" } } */
+/* { dg-final { scan-assembler-not "\\.L\[0-9\]+" } } */
--
2.34.1