[04/13] match-sat-alu.pd: Recognize borrow-test saturating subtraction
Commit Message
From: Kyrylo Tkachov <ktkachov@nvidia.com>
For unsigned subtraction, the wrapped difference is greater than the minuend
exactly when the subtraction borrows. These forms are therefore saturating
subtraction:
R = X - Y
R > X ? 0 : R
R <= X ? R : 0
Recognize both comparison operand orders and both result arm orders.
AArch64 -O3, vector body:
before:
sub v30.4s, v31.4s, v30.4s
cmhs v31.4s, v31.4s, v30.4s
and v31.16b, v31.16b, v30.16b
after:
uqsub v30.4s, v31.4s, v30.4s
Three vector operations become one.
Remove four existing expected-failure markers for the supported forms.
Bootstrapped and tested on aarch64-none-linux-gnu.
Ok for trunk?
gcc/ChangeLog:
* match-sat-alu.pd (unsigned_integer_sat_sub): Add the four
forms that compare the difference with the minuend.
gcc/testsuite/ChangeLog:
* gcc.target/aarch64/sat_u_sub_borrow-1.c: New test.
* gcc.target/aarch64/saturating_arithmetic_1.c: Remove the xfail
on usub.
* gcc.target/aarch64/sve/saturating_arithmetic_1.c: Remove the
xfail on usubq.
* gcc.target/aarch64/sve/saturating_arithmetic_3.c: Likewise.
* gcc.target/aarch64/sve/saturating_arithmetic_4.c: Likewise.
Signed-off-by: Kyrylo Tkachov <ktkachov@nvidia.com>
---
gcc/match-sat-alu.pd | 9 ++++++++
.../gcc.target/aarch64/sat_u_sub_borrow-1.c | 22 +++++++++++++++++++
.../aarch64/saturating_arithmetic_1.c | 2 +-
.../aarch64/sve/saturating_arithmetic_1.c | 2 +-
.../aarch64/sve/saturating_arithmetic_3.c | 2 +-
.../aarch64/sve/saturating_arithmetic_4.c | 2 +-
6 files changed, 35 insertions(+), 4 deletions(-)
create mode 100644 gcc/testsuite/gcc.target/aarch64/sat_u_sub_borrow-1.c
Comments
On Wed, Sep 2, 2026 at 7:55 AM <ktkachov@nvidia.com> wrote:
>
> From: Kyrylo Tkachov <ktkachov@nvidia.com>
>
> For unsigned subtraction, the wrapped difference is greater than the minuend
> exactly when the subtraction borrows. These forms are therefore saturating
> subtraction:
>
> R = X - Y
> R > X ? 0 : R
> R <= X ? R : 0
>
> Recognize both comparison operand orders and both result arm orders.
>
> AArch64 -O3, vector body:
>
> before:
>
> sub v30.4s, v31.4s, v30.4s
> cmhs v31.4s, v31.4s, v30.4s
> and v31.16b, v31.16b, v30.16b
>
> after:
>
> uqsub v30.4s, v31.4s, v30.4s
>
> Three vector operations become one.
> Remove four existing expected-failure markers for the supported forms.
>
> Bootstrapped and tested on aarch64-none-linux-gnu.
>
> Ok for trunk?
Ok.
>
> gcc/ChangeLog:
>
> * match-sat-alu.pd (unsigned_integer_sat_sub): Add the four
> forms that compare the difference with the minuend.
>
> gcc/testsuite/ChangeLog:
>
> * gcc.target/aarch64/sat_u_sub_borrow-1.c: New test.
> * gcc.target/aarch64/saturating_arithmetic_1.c: Remove the xfail
> on usub.
> * gcc.target/aarch64/sve/saturating_arithmetic_1.c: Remove the
> xfail on usubq.
> * gcc.target/aarch64/sve/saturating_arithmetic_3.c: Likewise.
> * gcc.target/aarch64/sve/saturating_arithmetic_4.c: Likewise.
>
> Signed-off-by: Kyrylo Tkachov <ktkachov@nvidia.com>
> ---
> gcc/match-sat-alu.pd | 9 ++++++++
> .../gcc.target/aarch64/sat_u_sub_borrow-1.c | 22 +++++++++++++++++++
> .../aarch64/saturating_arithmetic_1.c | 2 +-
> .../aarch64/sve/saturating_arithmetic_1.c | 2 +-
> .../aarch64/sve/saturating_arithmetic_3.c | 2 +-
> .../aarch64/sve/saturating_arithmetic_4.c | 2 +-
> 6 files changed, 35 insertions(+), 4 deletions(-)
> create mode 100644 gcc/testsuite/gcc.target/aarch64/sat_u_sub_borrow-1.c
>
> diff --git a/gcc/match-sat-alu.pd b/gcc/match-sat-alu.pd
> index 8c7bc0782a0..dfb2eb9800f 100644
> --- a/gcc/match-sat-alu.pd
> +++ b/gcc/match-sat-alu.pd
> @@ -131,6 +131,15 @@ along with GCC; see the file COPYING3. If not see
> /* SAT_U_SUB = (X - Y) * (X >= Y) */
> (mult:c (minus @0 @1) (convert (ge @0 @1)))
> (if (types_match (type, @0, @1))))
> + /* The wrapped difference exceeds the minuend exactly when the subtraction
> + borrows, so a source that names the difference and then tests it against
> + the minuend is a saturating subtract. */
> + (match (unsigned_integer_sat_sub @0 @1)
> + /* SAT_U_SUB = (X - Y) > X ? 0 : X - Y, and the commuted comparison. */
> + (cond^ (gt:c (minus@2 @0 @1) @0) integer_zerop @2))
> + (match (unsigned_integer_sat_sub @0 @1)
> + /* SAT_U_SUB = (X - Y) <= X ? X - Y : 0, and the commuted comparison. */
> + (cond^ (le:c (minus@2 @0 @1) @0) @2 integer_zerop))
> (match (unsigned_integer_sat_sub @0 @1)
> /* SAT_U_SUB = X - MIN (X, Y). The MIN has to be single use: while it
> stays live the saturating subtract is computed beside it instead of
> diff --git a/gcc/testsuite/gcc.target/aarch64/sat_u_sub_borrow-1.c b/gcc/testsuite/gcc.target/aarch64/sat_u_sub_borrow-1.c
> new file mode 100644
> index 00000000000..8b86cb92922
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/sat_u_sub_borrow-1.c
> @@ -0,0 +1,22 @@
> +/* { 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 wrapped difference is larger than the minuend exactly when the
> + subtraction borrowed, so each of these is a saturating subtract. */
> +
> +#define DEF(N, T) \
> + T f1_##N (T a, T b) { T r = a - b; return r > a ? 0 : r; } \
> + T f2_##N (T a, T b) { T r = a - b; if (r > a) r = 0; return r; } \
> + T f3_##N (T a, T b) { T r = a - b; return r <= a ? r : 0; }
> +
> +DEF (8, u8)
> +DEF (16, u16)
> +DEF (32, u32)
> +DEF (64, u64)
> +
> +/* { dg-final { scan-tree-dump-times "\\.SAT_SUB " 12 "optimized" } } */
> diff --git a/gcc/testsuite/gcc.target/aarch64/saturating_arithmetic_1.c b/gcc/testsuite/gcc.target/aarch64/saturating_arithmetic_1.c
> index 8fc1569845b..c73a024edec 100644
> --- a/gcc/testsuite/gcc.target/aarch64/saturating_arithmetic_1.c
> +++ b/gcc/testsuite/gcc.target/aarch64/saturating_arithmetic_1.c
> @@ -19,7 +19,7 @@
> ** ret
> */
> /*
> -** usub: { xfail *-*-* }
> +** usub:
> ** dup v([0-9]+).8b, w[01]
> ** dup v([0-9]+).8b, w[01]
> ** uqsub b([0-9]+), b\1, b\2
> diff --git a/gcc/testsuite/gcc.target/aarch64/sve/saturating_arithmetic_1.c b/gcc/testsuite/gcc.target/aarch64/sve/saturating_arithmetic_1.c
> index 6936e9a2704..4cfab740c40 100644
> --- a/gcc/testsuite/gcc.target/aarch64/sve/saturating_arithmetic_1.c
> +++ b/gcc/testsuite/gcc.target/aarch64/sve/saturating_arithmetic_1.c
> @@ -39,7 +39,7 @@
> ** ...
> */
> /*
> -** usubq: { xfail *-*-* }
> +** usubq:
> ** ...
> ** ld1b\tz([0-9]+)\.b, .*
> ** ld1b\tz([0-9]+)\.b, .*
> diff --git a/gcc/testsuite/gcc.target/aarch64/sve/saturating_arithmetic_3.c b/gcc/testsuite/gcc.target/aarch64/sve/saturating_arithmetic_3.c
> index 14e2de59b1e..def7c82e256 100644
> --- a/gcc/testsuite/gcc.target/aarch64/sve/saturating_arithmetic_3.c
> +++ b/gcc/testsuite/gcc.target/aarch64/sve/saturating_arithmetic_3.c
> @@ -40,7 +40,7 @@
> ** ...
> */
> /*
> -** usubq: { xfail *-*-* }
> +** usubq:
> ** ...
> ** ld1w\tz([0-9]+)\.s, .*
> ** ld1w\tz([0-9]+)\.s, .*
> diff --git a/gcc/testsuite/gcc.target/aarch64/sve/saturating_arithmetic_4.c b/gcc/testsuite/gcc.target/aarch64/sve/saturating_arithmetic_4.c
> index 05a5786b4ab..5b9f8740ed4 100644
> --- a/gcc/testsuite/gcc.target/aarch64/sve/saturating_arithmetic_4.c
> +++ b/gcc/testsuite/gcc.target/aarch64/sve/saturating_arithmetic_4.c
> @@ -40,7 +40,7 @@
> ** ...
> */
> /*
> -** usubq: { xfail *-*-* }
> +** usubq:
> ** ...
> ** ld1d\tz([0-9]+)\.d, .*
> ** ld1d\tz([0-9]+)\.d, .*
> --
> 2.50.1 (Apple Git-155)
>
@@ -131,6 +131,15 @@ along with GCC; see the file COPYING3. If not see
/* SAT_U_SUB = (X - Y) * (X >= Y) */
(mult:c (minus @0 @1) (convert (ge @0 @1)))
(if (types_match (type, @0, @1))))
+ /* The wrapped difference exceeds the minuend exactly when the subtraction
+ borrows, so a source that names the difference and then tests it against
+ the minuend is a saturating subtract. */
+ (match (unsigned_integer_sat_sub @0 @1)
+ /* SAT_U_SUB = (X - Y) > X ? 0 : X - Y, and the commuted comparison. */
+ (cond^ (gt:c (minus@2 @0 @1) @0) integer_zerop @2))
+ (match (unsigned_integer_sat_sub @0 @1)
+ /* SAT_U_SUB = (X - Y) <= X ? X - Y : 0, and the commuted comparison. */
+ (cond^ (le:c (minus@2 @0 @1) @0) @2 integer_zerop))
(match (unsigned_integer_sat_sub @0 @1)
/* SAT_U_SUB = X - MIN (X, Y). The MIN has to be single use: while it
stays live the saturating subtract is computed beside it instead of
new file mode 100644
@@ -0,0 +1,22 @@
+/* { 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 wrapped difference is larger than the minuend exactly when the
+ subtraction borrowed, so each of these is a saturating subtract. */
+
+#define DEF(N, T) \
+ T f1_##N (T a, T b) { T r = a - b; return r > a ? 0 : r; } \
+ T f2_##N (T a, T b) { T r = a - b; if (r > a) r = 0; return r; } \
+ T f3_##N (T a, T b) { T r = a - b; return r <= a ? r : 0; }
+
+DEF (8, u8)
+DEF (16, u16)
+DEF (32, u32)
+DEF (64, u64)
+
+/* { dg-final { scan-tree-dump-times "\\.SAT_SUB " 12 "optimized" } } */
@@ -19,7 +19,7 @@
** ret
*/
/*
-** usub: { xfail *-*-* }
+** usub:
** dup v([0-9]+).8b, w[01]
** dup v([0-9]+).8b, w[01]
** uqsub b([0-9]+), b\1, b\2
@@ -39,7 +39,7 @@
** ...
*/
/*
-** usubq: { xfail *-*-* }
+** usubq:
** ...
** ld1b\tz([0-9]+)\.b, .*
** ld1b\tz([0-9]+)\.b, .*
@@ -40,7 +40,7 @@
** ...
*/
/*
-** usubq: { xfail *-*-* }
+** usubq:
** ...
** ld1w\tz([0-9]+)\.s, .*
** ld1w\tz([0-9]+)\.s, .*
@@ -40,7 +40,7 @@
** ...
*/
/*
-** usubq: { xfail *-*-* }
+** usubq:
** ...
** ld1d\tz([0-9]+)\.d, .*
** ld1d\tz([0-9]+)\.d, .*