https://gcc.gnu.org/g:87c97883d659edc4b6d5cbece74d7ab7a3584a75

commit r17-4081-g87c97883d659edc4b6d5cbece74d7ab7a3584a75
Author: Kyrylo Tkachov <[email protected]>
Date:   Thu Jul 30 18:13:04 2026 +0200

    match.pd: Narrow promoted saturating addition and subtraction
    
    Integer promotions and explicit vector conversions can leave a narrow
    saturating operation as extensions, a wide operation, a clamp, and a
    truncation.  Simplify these forms:
    
      (type) MAX ((wide_type) A - (wide_type) B, 0)
        -> A - MIN (A, B)
      (type) MIN ((wide_type) A + (wide_type) B, MAX)
        -> A + MIN (~A, B)
    
    Require an intermediate type in which the operation cannot overflow.
    Subtraction needs a wider signed type.  Addition needs one extra value bit 
and
    a further sign bit for a signed intermediate.  Also require the exact 
maximum
    of the narrow type and one use of the outer clamp.  For vector types, 
require
    target support for each narrow operation that the result introduces.
    
    The result exposes narrow saturating forms.
    
    AArch64 -O3, vector body:
    
    before:
    
            ldr     q30, [x1, x4]
            ldr     q29, [x2, x4]
            usubl   v28.8h, v30.8b, v29.8b
            usubl2  v29.8h, v30.16b, v29.16b
            smax    v28.8h, v28.8h, v31.8h
            smax    v29.8h, v29.8h, v31.8h
            uzp1    v29.16b, v28.16b, v29.16b
            str     q29, [x0, x4]
    
    after:
    
            ldr     q31, [x1, x4]
            ldr     q30, [x2, x4]
            uqsub   v30.16b, v31.16b, v30.16b
            str     q30, [x0, x4]
    
    The five computing instructions become one.  Direct vector forms also remove
    the wide operations and conversions.  Targets without a saturating
    instruction still use fewer operations.
    
    Scalar byte and halfword forms on AArch64 can cost one extra instruction
    because the backend uses SIMD registers.  The direct narrow form has the 
same
    cost.
    
    Bootstrapped and tested on aarch64-none-linux-gnu.
    
    gcc/ChangeLog:
    
            * match.pd ((type) MAX ((wide_type) a - (wide_type) b, 0)): New
            simplification.
            ((type) MIN ((wide_type) a + (wide_type) b, MAX)): Likewise.
    
    gcc/testsuite/ChangeLog:
    
            * gcc.target/aarch64/sat_u_promoted-1.c: New test.
            * gcc.target/aarch64/sat_u_promoted-2.c: New test.
            * gcc.target/aarch64/sat_u_promoted-3.c: New test.
            * g++.target/aarch64/sat_u_promoted-vector-1.C: New test.
    
    Signed-off-by: Kyrylo Tkachov <[email protected]>

Diff:
---
 gcc/match.pd                                       | 46 ++++++++++++++++++++++
 .../g++.target/aarch64/sat_u_promoted-vector-1.C   | 35 ++++++++++++++++
 .../gcc.target/aarch64/sat_u_promoted-1.c          | 29 ++++++++++++++
 .../gcc.target/aarch64/sat_u_promoted-2.c          | 32 +++++++++++++++
 .../gcc.target/aarch64/sat_u_promoted-3.c          | 26 ++++++++++++
 5 files changed, 168 insertions(+)

diff --git a/gcc/match.pd b/gcc/match.pd
index 032f29dcbc25..8278cf1fd6db 100644
--- a/gcc/match.pd
+++ b/gcc/match.pd
@@ -4821,6 +4821,52 @@ DEFINE_INT_AND_FLOAT_ROUND_FN (RINT)
     @0
     @2)))
 
+/* Narrow a saturating add or subtract that promotions or explicit vector
+   conversions widened.
+   For an unsigned type,
+     (type) MAX ((wide_type) a - (wide_type) b, 0) -> a - MIN (a, b)
+     (type) MIN ((wide_type) a + (wide_type) b, MAX) -> a + MIN (~a, b)
+   Both results are exact in the narrow type: the difference is clamped at
+   zero and the sum at MAX, so the truncation never loses a bit.  Require the
+   outer clamp to have one use.  For a vector, also require target support for
+   each narrow operation that the result introduces.  */
+(simplify
+ (convert (max:s (minus@2 (convert @0) (convert @1)) integer_zerop))
+ (if (ANY_INTEGRAL_TYPE_P (type)
+      && TYPE_UNSIGNED (type)
+      && types_match (type, @0, @1)
+      && !TYPE_UNSIGNED (TREE_TYPE (@2))
+      && element_precision (TREE_TYPE (@2)) > element_precision (type)
+      && (!VECTOR_TYPE_P (type)
+         || (target_supports_op_p (type, MIN_EXPR, optab_vector)
+             && target_supports_op_p (type, MINUS_EXPR, optab_vector))))
+  (minus @0 (min @0 @1))))
+
+(simplify
+ (convert (min:s (plus@2 (convert @0) (convert @1))
+                uniform_integer_cst_p@3))
+ (if (ANY_INTEGRAL_TYPE_P (type)
+      && TYPE_UNSIGNED (type)
+      && types_match (type, @0, @1)
+      && (!VECTOR_TYPE_P (type)
+         || (target_supports_op_p (type, BIT_NOT_EXPR, optab_vector)
+             && target_supports_op_p (type, MIN_EXPR, optab_vector)
+             && target_supports_op_p (type, PLUS_EXPR, optab_vector))))
+  (with
+   {
+    unsigned precision = element_precision (type);
+    tree wide_type = TREE_TYPE (@2);
+    unsigned wide_precision = element_precision (wide_type);
+    tree max_cst = uniform_integer_cst_p (@3);
+    /* The widened sum reaches 2 * (2^precision - 1), which needs one bit
+       more than the narrow type, and one further bit for a sign.  */
+    unsigned needed = precision + (TYPE_UNSIGNED (wide_type) ? 1 : 2);
+   }
+   (if (wide_precision >= needed
+       && wi::eq_p (wi::to_wide (max_cst),
+                    wi::mask (precision, false, wide_precision)))
+    (plus @0 (min @1 (bit_not @0)))))))
+
 /* MIN (MAX (X, Y + CST), Y) -> Y for a positive CST, and the dual
    MAX (MIN (X, Y + CST), Y) -> Y for a negative one.  The inner select is
    at least Y + CST, which the absence of wrapping puts strictly beyond Y,
diff --git a/gcc/testsuite/g++.target/aarch64/sat_u_promoted-vector-1.C 
b/gcc/testsuite/g++.target/aarch64/sat_u_promoted-vector-1.C
new file mode 100644
index 000000000000..b55f3667426e
--- /dev/null
+++ b/gcc/testsuite/g++.target/aarch64/sat_u_promoted-vector-1.C
@@ -0,0 +1,35 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+
+typedef unsigned char u8x8 __attribute__((vector_size(8)));
+typedef short i16x8 __attribute__((vector_size(16)));
+
+u8x8
+subv (u8x8 a, u8x8 b)
+{
+  i16x8 x = __builtin_convertvector (a, i16x8);
+  i16x8 y = __builtin_convertvector (b, i16x8);
+  i16x8 d = x - y;
+  i16x8 zero = { 0, 0, 0, 0, 0, 0, 0, 0 };
+  i16x8 r = d < zero ? zero : d;
+  return __builtin_convertvector (r, u8x8);
+}
+
+u8x8
+addv (u8x8 a, u8x8 b)
+{
+  i16x8 x = __builtin_convertvector (a, i16x8);
+  i16x8 y = __builtin_convertvector (b, i16x8);
+  i16x8 s = x + y;
+  i16x8 max = { 255, 255, 255, 255, 255, 255, 255, 255 };
+  i16x8 r = s > max ? max : s;
+  return __builtin_convertvector (r, u8x8);
+}
+
+/* { dg-final { scan-tree-dump-times "MIN_EXPR" 2 "optimized" } } */
+/* { dg-final { scan-tree-dump-not "MAX_EXPR" "optimized" } } */
+/* { dg-final { scan-tree-dump-not "i16x8" "optimized" } } */
+/* { dg-final { scan-assembler-times {\tumin\tv[0-9]+\.8b} 2 } } */
+/* { dg-final { scan-assembler-times {\tsub\tv[0-9]+\.8b} 1 } } */
+/* { dg-final { scan-assembler-times {\tnot\tv[0-9]+\.8b} 1 } } */
+/* { dg-final { scan-assembler-times {\tadd\tv[0-9]+\.8b} 1 } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/sat_u_promoted-1.c 
b/gcc/testsuite/gcc.target/aarch64/sat_u_promoted-1.c
new file mode 100644
index 000000000000..424d2df3fba3
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/sat_u_promoted-1.c
@@ -0,0 +1,29 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+
+typedef unsigned char u8;
+typedef unsigned short u16;
+typedef unsigned int u32;
+typedef unsigned long long u64;
+
+/* The integer promotions turn a narrow saturating add or subtract written
+   as a clamp into a widened operation feeding a MIN or a MAX.  */
+
+u8 sub8 (u8 a, u8 b) { int t = a - b; return t < 0 ? 0 : t; }
+u8 sub8_swapped (u8 a, u8 b) { int t = a - b; return t > 0 ? t : 0; }
+u16 sub16 (u16 a, u16 b) { int t = a - b; return t < 0 ? 0 : t; }
+u32 sub32 (u32 a, u32 b) { long long t = (long long) a - b; return t < 0 ? 0 : 
t; }
+
+u8 add8 (u8 a, u8 b) { int t = a + b; return t > 255 ? 255 : t; }
+u16 add16 (u16 a, u16 b) { int t = a + b; return t > 65535 ? 65535 : t; }
+u32 add32 (u32 a, u32 b) { u64 t = (u64) a + b; return t > 0xffffffffu ? 
0xffffffffu : t; }
+
+u8 add8_shared (u8 a, u8 b, int *p)
+{
+  int t = a + b;
+  *p = t;
+  return t > 255 ? 255 : t;
+}
+
+/* { dg-final { scan-tree-dump-times "\\.SAT_SUB " 4 "optimized" } } */
+/* { dg-final { scan-tree-dump-times "\\.SAT_ADD " 4 "optimized" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/sat_u_promoted-2.c 
b/gcc/testsuite/gcc.target/aarch64/sat_u_promoted-2.c
new file mode 100644
index 000000000000..0e7a3c701c9e
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/sat_u_promoted-2.c
@@ -0,0 +1,32 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -fdump-tree-optimized" } */
+
+typedef unsigned char u8;
+
+/* These loops expose narrow saturating operations that vectorize to one
+   uqsub or uqadd.  */
+
+void
+sub_loop (u8 *__restrict d, u8 *__restrict a, u8 *__restrict b, int n)
+{
+  for (int i = 0; i < n; i++)
+    {
+      int t = a[i] - b[i];
+      d[i] = t < 0 ? 0 : t;
+    }
+}
+
+void
+add_loop (u8 *__restrict d, u8 *__restrict a, u8 *__restrict b, int n)
+{
+  for (int i = 0; i < n; i++)
+    {
+      int t = a[i] + b[i];
+      d[i] = t > 255 ? 255 : t;
+    }
+}
+
+/* { dg-final { scan-tree-dump "\\.SAT_SUB " "optimized" } } */
+/* { dg-final { scan-tree-dump "\\.SAT_ADD " "optimized" } } */
+/* { dg-final { scan-assembler "uqsub\tv\[0-9\]+\.16b" } } */
+/* { dg-final { scan-assembler "uqadd\tv\[0-9\]+\.16b" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/sat_u_promoted-3.c 
b/gcc/testsuite/gcc.target/aarch64/sat_u_promoted-3.c
new file mode 100644
index 000000000000..4ec4abc36bcd
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/sat_u_promoted-3.c
@@ -0,0 +1,26 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+
+typedef unsigned char u8;
+
+/* Keep the outer clamp live so that the narrowing rules do not fire.  */
+
+u8
+sub8_result_shared (u8 a, u8 b, int *out)
+{
+  int t = a - b;
+  int r = t < 0 ? 0 : t;
+  *out = r;
+  return r;
+}
+
+u8
+add8_result_shared (u8 a, u8 b, int *out)
+{
+  int t = a + b;
+  int r = t > 255 ? 255 : t;
+  *out = r;
+  return r;
+}
+
+/* { dg-final { scan-tree-dump-not "\\.SAT_" "optimized" } } */

Reply via email to