[05/13] match-sat-alu.pd: Recognize more unsigned saturating subtraction forms

Message ID 20260902145254.77832-7-ktkachov@nvidia.com
State New
Headers
Series Saturating arithmetic matching improvements |

Commit Message

Kyrylo Tkachov Sept. 2, 2026, 2:52 p.m. UTC
  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

Andrea Pinski Sept. 3, 2026, 5:55 a.m. UTC | #1
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)
>
  

Patch

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" } } */