[05/13] match-sat-alu.pd: Recognize more unsigned saturating subtraction forms
Commit Message
From: Kyrylo Tkachov <ktkachov@nvidia.com>
Equivalent unsigned saturating subtraction forms can reverse the comparison
operands or the conditional arms:
B < A ? A - B : 0
A < B ? 0 : A - B
The middle end can keep either comparison operand order. Control flow with
two returns can also reverse the conditional arms.
Recognize both forms and reversed comparisons in the multiply-by-predicate
form.
AArch64 -O2:
before:
sub w2, w0, w1
cmp w1, w0
csel w0, w2, wzr, cc
after:
subs w0, w0, w1
csel w0, w0, wzr, cs
The vector form replaces a comparison, a subtraction, and an AND with one
UQSUB.
Bootstrapped and tested on aarch64-none-linux-gnu.
Ok for trunk?
gcc/ChangeLog:
* match-sat-alu.pd (unsigned_integer_sat_sub): Accept commuted
comparison operand orders and the arm order from a two-return source.
gcc/testsuite/ChangeLog:
* gcc.target/aarch64/sat_u_sub_swapped-1.c: New test.
Signed-off-by: Kyrylo Tkachov <ktkachov@nvidia.com>
---
gcc/match-sat-alu.pd | 29 +++++++++++++++++++
.../gcc.target/aarch64/sat_u_sub_swapped-1.c | 27 +++++++++++++++++
2 files changed, 56 insertions(+)
create mode 100644 gcc/testsuite/gcc.target/aarch64/sat_u_sub_swapped-1.c
Comments
On Wed, Sep 2, 2026 at 7:57 AM <ktkachov@nvidia.com> wrote:
>
> From: Kyrylo Tkachov <ktkachov@nvidia.com>
>
> Equivalent unsigned saturating subtraction forms can reverse the comparison
> operands or the conditional arms:
>
> B < A ? A - B : 0
> A < B ? 0 : A - B
>
> The middle end can keep either comparison operand order. Control flow with
> two returns can also reverse the conditional arms.
>
> Recognize both forms and reversed comparisons in the multiply-by-predicate
> form.
>
> AArch64 -O2:
>
> before:
>
> sub w2, w0, w1
> cmp w1, w0
> csel w0, w2, wzr, cc
>
> after:
>
> subs w0, w0, w1
> csel w0, w0, wzr, cs
>
> The vector form replaces a comparison, a subtraction, and an AND with one
> UQSUB.
>
> Bootstrapped and tested on aarch64-none-linux-gnu.
>
> Ok for trunk?
>
> gcc/ChangeLog:
>
> * match-sat-alu.pd (unsigned_integer_sat_sub): Accept commuted
> comparison operand orders and the arm order from a two-return source.
>
> gcc/testsuite/ChangeLog:
>
> * gcc.target/aarch64/sat_u_sub_swapped-1.c: New test.
So for:
(match (unsigned_integer_sat_sub @0 @1)
/* SAT_U_SUB = X > Y ? X - Y : 0 */
(cond^ (gt @0 @1) (minus @0 @1) integer_zerop)
You should be able to just add a :c on the gt to do the same matching
as you are doing manually.
Creating new patterns is harder to understand really.
The swapped 2nd and 3rd cond operands are harder and we really need to
come up with a correct fix there still.
>
> Signed-off-by: Kyrylo Tkachov <ktkachov@nvidia.com>
> ---
> gcc/match-sat-alu.pd | 29 +++++++++++++++++++
> .../gcc.target/aarch64/sat_u_sub_swapped-1.c | 27 +++++++++++++++++
> 2 files changed, 56 insertions(+)
> create mode 100644 gcc/testsuite/gcc.target/aarch64/sat_u_sub_swapped-1.c
>
> diff --git a/gcc/match-sat-alu.pd b/gcc/match-sat-alu.pd
> index dfb2eb9800f..b3dd8db0d27 100644
> --- a/gcc/match-sat-alu.pd
> +++ b/gcc/match-sat-alu.pd
> @@ -131,6 +131,35 @@ 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))))
> + /* Accept the comparison with its operands in the opposite order. */
> + (match (unsigned_integer_sat_sub @0 @1)
> + (cond^ (lt @1 @0) (minus @0 @1) integer_zerop)
> + (if (types_match (type, @0, @1))))
> + (match (unsigned_integer_sat_sub @0 @1)
> + (cond^ (le @1 @0) (convert? (minus (convert1? @0) (convert1? @1)))
> + integer_zerop)
> + (if (TYPE_UNSIGNED (TREE_TYPE (@0)) && types_match (@0, @1))))
> + (match (unsigned_integer_sat_sub @0 @1)
> + (mult:c (minus @0 @1) (convert (lt @1 @0)))
> + (if (types_match (type, @0, @1))))
> + (match (unsigned_integer_sat_sub @0 @1)
> + (mult:c (minus @0 @1) (convert (le @1 @0)))
> + (if (types_match (type, @0, @1))))
> + /* A source with two returns can keep zero on the taken arm. */
> + (match (unsigned_integer_sat_sub @0 @1)
> + (cond^ (lt @0 @1) integer_zerop (minus @0 @1))
> + (if (types_match (type, @0, @1))))
> + (match (unsigned_integer_sat_sub @0 @1)
> + (cond^ (le @0 @1) integer_zerop
> + (convert? (minus (convert1? @0) (convert1? @1))))
> + (if (TYPE_UNSIGNED (TREE_TYPE (@0)) && types_match (@0, @1))))
> + (match (unsigned_integer_sat_sub @0 @1)
> + (cond^ (gt @1 @0) integer_zerop (minus @0 @1))
> + (if (types_match (type, @0, @1))))
> + (match (unsigned_integer_sat_sub @0 @1)
> + (cond^ (ge @1 @0) integer_zerop
> + (convert? (minus (convert1? @0) (convert1? @1))))
> + (if (TYPE_UNSIGNED (TREE_TYPE (@0)) && types_match (@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. */
> diff --git a/gcc/testsuite/gcc.target/aarch64/sat_u_sub_swapped-1.c b/gcc/testsuite/gcc.target/aarch64/sat_u_sub_swapped-1.c
> new file mode 100644
> index 00000000000..fb42b6b07e3
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/sat_u_sub_swapped-1.c
> @@ -0,0 +1,27 @@
> +/* { 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 same saturating subtract with the subtrahend written on the left of
> + the comparison. GIMPLE orders comparison operands by SSA version, so
> + which spelling survives does not follow the source. */
> +
> +#define DEF(N, T) \
> + T f1_##N (T a, T b) { return b < a ? a - b : 0; } \
> + T f2_##N (T a, T b) { return b <= a ? a - b : 0; } \
> + T f3_##N (T a, T b) { return (T) ((a - b) * (T) (b < a)); } \
> + T f4_##N (T a, T b) { return (T) ((a - b) * (T) (b <= a)); } \
> + T f5_##N (T a, T b) { if (b >= a) return 0; return a - b; } \
> + T f6_##N (T a, T b) { return b >= a ? 0 : a - b; } \
> + T f7_##N (T a, T b) { if (a < b) return 0; return a - b; }
> +
> +DEF (8, u8)
> +DEF (16, u16)
> +DEF (32, u32)
> +DEF (64, u64)
> +
> +/* { dg-final { scan-tree-dump-times "\\.SAT_SUB " 28 "optimized" } } */
> --
> 2.50.1 (Apple Git-155)
>
@@ -131,6 +131,35 @@ 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))))
+ /* Accept the comparison with its operands in the opposite order. */
+ (match (unsigned_integer_sat_sub @0 @1)
+ (cond^ (lt @1 @0) (minus @0 @1) integer_zerop)
+ (if (types_match (type, @0, @1))))
+ (match (unsigned_integer_sat_sub @0 @1)
+ (cond^ (le @1 @0) (convert? (minus (convert1? @0) (convert1? @1)))
+ integer_zerop)
+ (if (TYPE_UNSIGNED (TREE_TYPE (@0)) && types_match (@0, @1))))
+ (match (unsigned_integer_sat_sub @0 @1)
+ (mult:c (minus @0 @1) (convert (lt @1 @0)))
+ (if (types_match (type, @0, @1))))
+ (match (unsigned_integer_sat_sub @0 @1)
+ (mult:c (minus @0 @1) (convert (le @1 @0)))
+ (if (types_match (type, @0, @1))))
+ /* A source with two returns can keep zero on the taken arm. */
+ (match (unsigned_integer_sat_sub @0 @1)
+ (cond^ (lt @0 @1) integer_zerop (minus @0 @1))
+ (if (types_match (type, @0, @1))))
+ (match (unsigned_integer_sat_sub @0 @1)
+ (cond^ (le @0 @1) integer_zerop
+ (convert? (minus (convert1? @0) (convert1? @1))))
+ (if (TYPE_UNSIGNED (TREE_TYPE (@0)) && types_match (@0, @1))))
+ (match (unsigned_integer_sat_sub @0 @1)
+ (cond^ (gt @1 @0) integer_zerop (minus @0 @1))
+ (if (types_match (type, @0, @1))))
+ (match (unsigned_integer_sat_sub @0 @1)
+ (cond^ (ge @1 @0) integer_zerop
+ (convert? (minus (convert1? @0) (convert1? @1))))
+ (if (TYPE_UNSIGNED (TREE_TYPE (@0)) && types_match (@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. */
new file mode 100644
@@ -0,0 +1,27 @@
+/* { 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 same saturating subtract with the subtrahend written on the left of
+ the comparison. GIMPLE orders comparison operands by SSA version, so
+ which spelling survives does not follow the source. */
+
+#define DEF(N, T) \
+ T f1_##N (T a, T b) { return b < a ? a - b : 0; } \
+ T f2_##N (T a, T b) { return b <= a ? a - b : 0; } \
+ T f3_##N (T a, T b) { return (T) ((a - b) * (T) (b < a)); } \
+ T f4_##N (T a, T b) { return (T) ((a - b) * (T) (b <= a)); } \
+ T f5_##N (T a, T b) { if (b >= a) return 0; return a - b; } \
+ T f6_##N (T a, T b) { return b >= a ? 0 : a - b; } \
+ T f7_##N (T a, T b) { if (a < b) return 0; return a - b; }
+
+DEF (8, u8)
+DEF (16, u16)
+DEF (32, u32)
+DEF (64, u64)
+
+/* { dg-final { scan-tree-dump-times "\\.SAT_SUB " 28 "optimized" } } */