On Fri, Sep 11, 2026 at 6:10 AM <[email protected]> wrote: > > From: Kyrylo Tkachov <[email protected]> > > X - MIN (X, Y) is unsigned saturating subtraction, but subtraction > statements were not checked for this form. > > Recognize the form and check MINUS_EXPR statements for saturation. Require > the MIN to have one use so that the replacement removes it. Make the assign > recognizers report whether they replaced the statement, and do not run later > transforms after a replacement. > > AArch64 -O2: > > before: > > cmp w1, w0 > csel w1, w1, w0, ls > sub w0, w0, w1 > > after: > > subs w0, w0, w1 > csel w0, w0, wzr, cs > > The vector form becomes one UQSUB instead of a comparison, a select, and a > subtraction. > > Bootstrapped and tested on aarch64-none-linux-gnu. > > Ok for trunk?
Ok. > > Changes since v2: > - Rebase on current trunk. > > gcc/ChangeLog: > > * match-sat-alu.pd (unsigned_integer_sat_sub): Add the > X - MIN (X, Y) form. > * tree-ssa-math-opts.cc > (build_saturation_binary_arith_call_and_replace): Return whether a > replacement was made. > (match_saturation_add_with_assign): Likewise. > (match_unsigned_saturation_sub): Likewise. > (math_opts_dom_walker::after_dom_children): Check MINUS_EXPR for > saturation, and use the assign recognizer results. > > gcc/testsuite/ChangeLog: > > * gcc.target/aarch64/sat_u_sub_minmax-1.c: New test. > * gcc.target/aarch64/sat_u_sub_minmax-2.c: New test. > * gcc.target/aarch64/sat_u_sub_minmax-3.c: New test. > > Signed-off-by: Kyrylo Tkachov <[email protected]> > --- > gcc/match-sat-alu.pd | 6 ++++ > .../gcc.target/aarch64/sat_u_sub_minmax-1.c | 18 ++++++++++ > .../gcc.target/aarch64/sat_u_sub_minmax-2.c | 32 +++++++++++++++++ > .../gcc.target/aarch64/sat_u_sub_minmax-3.c | 17 +++++++++ > gcc/tree-ssa-math-opts.cc | 36 ++++++++++++------- > 5 files changed, 97 insertions(+), 12 deletions(-) > create mode 100644 gcc/testsuite/gcc.target/aarch64/sat_u_sub_minmax-1.c > create mode 100644 gcc/testsuite/gcc.target/aarch64/sat_u_sub_minmax-2.c > create mode 100644 gcc/testsuite/gcc.target/aarch64/sat_u_sub_minmax-3.c > > diff --git a/gcc/match-sat-alu.pd b/gcc/match-sat-alu.pd > index 2a37d23a869..f7ee8d63f1e 100644 > --- a/gcc/match-sat-alu.pd > +++ b/gcc/match-sat-alu.pd > @@ -169,6 +169,12 @@ along with GCC; see the file COPYING3. If not see > (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 > + replacing it, which costs an instruction. */ > + (minus @0 (min:c@2 @0 @1)) > + (if (single_use (@2)))) > (match (unsigned_integer_sat_sub @0 @1) > /* DIFF = SUB_OVERFLOW (X, Y) > SAT_U_SUB = REALPART (DIFF) | (IMAGPART (DIFF) + (-1)) */ > diff --git a/gcc/testsuite/gcc.target/aarch64/sat_u_sub_minmax-1.c > b/gcc/testsuite/gcc.target/aarch64/sat_u_sub_minmax-1.c > new file mode 100644 > index 00000000000..aa1d7b6f031 > --- /dev/null > +++ b/gcc/testsuite/gcc.target/aarch64/sat_u_sub_minmax-1.c > @@ -0,0 +1,18 @@ > +/* { dg-do compile } */ > +/* { dg-options "-O2 -fdump-tree-optimized" } */ > + > +#define DEF_MIN(T) \ > + T min_##T (T a, T b) { return a - (a < b ? a : b); } \ > + T nim_##T (T a, T b) { return a - (b < a ? b : a); } > + > +typedef unsigned char u8; > +typedef unsigned short u16; > +typedef unsigned int u32; > +typedef unsigned long long u64; > + > +DEF_MIN (u8) > +DEF_MIN (u16) > +DEF_MIN (u32) > +DEF_MIN (u64) > + > +/* { dg-final { scan-tree-dump-times "\\.SAT_SUB " 8 "optimized" } } */ > diff --git a/gcc/testsuite/gcc.target/aarch64/sat_u_sub_minmax-2.c > b/gcc/testsuite/gcc.target/aarch64/sat_u_sub_minmax-2.c > new file mode 100644 > index 00000000000..dcf8ab46309 > --- /dev/null > +++ b/gcc/testsuite/gcc.target/aarch64/sat_u_sub_minmax-2.c > @@ -0,0 +1,32 @@ > +/* { dg-do compile } */ > +/* { dg-options "-O3 -fdump-tree-optimized" } */ > + > +typedef unsigned char u8; > +typedef unsigned int u32; > + > +/* The recognition must reach the vectoriser, so that the loop becomes a > + single uqsub rather than a compare, a select and a subtraction. */ > + > +void > +min_loop (u8 *__restrict d, u8 *__restrict a, u8 *__restrict b, int n) > +{ > + for (int i = 0; i < n; i++) > + { > + u8 x = a[i], y = b[i]; > + d[i] = x - (x < y ? x : y); > + } > +} > + > +void > +min_loop32 (u32 *__restrict d, u32 *__restrict a, u32 *__restrict b, int n) > +{ > + for (int i = 0; i < n; i++) > + { > + u32 x = a[i], y = b[i]; > + d[i] = x - (y < x ? y : x); > + } > +} > + > +/* { dg-final { scan-tree-dump "\\.SAT_SUB " "optimized" } } */ > +/* { dg-final { scan-assembler "uqsub\tv\[0-9\]+\\.16b" } } */ > +/* { dg-final { scan-assembler "uqsub\tv\[0-9\]+\\.4s" } } */ > diff --git a/gcc/testsuite/gcc.target/aarch64/sat_u_sub_minmax-3.c > b/gcc/testsuite/gcc.target/aarch64/sat_u_sub_minmax-3.c > new file mode 100644 > index 00000000000..ca71343699d > --- /dev/null > +++ b/gcc/testsuite/gcc.target/aarch64/sat_u_sub_minmax-3.c > @@ -0,0 +1,17 @@ > +/* { dg-do compile } */ > +/* { dg-options "-O2 -fdump-tree-optimized" } */ > + > +typedef unsigned int u32; > + > +/* While the MIN stays live the saturating subtract would be computed beside > + it rather than instead of it, so the rules do not fire. */ > + > +u32 > +min_live (u32 a, u32 b, u32 *o) > +{ > + u32 m = a < b ? a : b; > + *o = m; > + return a - m; > +} > + > +/* { dg-final { scan-tree-dump-not "\\.SAT_SUB " "optimized" } } */ > diff --git a/gcc/tree-ssa-math-opts.cc b/gcc/tree-ssa-math-opts.cc > index 70ac17c9e2f..9c959cc8450 100644 > --- a/gcc/tree-ssa-math-opts.cc > +++ b/gcc/tree-ssa-math-opts.cc > @@ -4123,7 +4123,7 @@ extern bool gimple_signed_integer_sat_add (tree, tree*, > tree (*)(tree)); > extern bool gimple_signed_integer_sat_sub (tree, tree*, tree (*)(tree)); > extern bool gimple_signed_integer_sat_trunc (tree, tree*, tree (*)(tree)); > > -static void > +static bool > build_saturation_binary_arith_call_and_replace (gimple_stmt_iterator *gsi, > internal_fn fn, tree lhs, > tree op_0, tree op_1) > @@ -4133,7 +4133,10 @@ build_saturation_binary_arith_call_and_replace > (gimple_stmt_iterator *gsi, > gcall *call = gimple_build_call_internal (fn, 2, op_0, op_1); > gimple_call_set_lhs (call, lhs); > gsi_replace (gsi, call, /* update_eh_info */ true); > + return true; > } > + > + return false; > } > > static bool > @@ -4177,9 +4180,10 @@ build_saturation_binary_arith_call_and_insert > (gimple_stmt_iterator *gsi, > * # _9 = PHI <_2(3), 128(2)> > * _4 = (int8_t) _9; > * => > - * _4 = .SAT_ADD (x_5, -1); */ > + * _4 = .SAT_ADD (x_5, -1); > + * Return true if the statement was replaced. */ > > -static void > +static bool > match_saturation_add_with_assign (gimple_stmt_iterator *gsi, gassign *stmt) > { > tree ops[2]; > @@ -4187,8 +4191,10 @@ match_saturation_add_with_assign (gimple_stmt_iterator > *gsi, gassign *stmt) > > if (gimple_unsigned_integer_sat_add (lhs, ops, NULL) > || gimple_signed_integer_sat_add (lhs, ops, NULL)) > - build_saturation_binary_arith_call_and_replace (gsi, IFN_SAT_ADD, lhs, > - ops[0], ops[1]); > + return build_saturation_binary_arith_call_and_replace (gsi, IFN_SAT_ADD, > + lhs, ops[0], > ops[1]); > + > + return false; > } > > /* > @@ -4257,17 +4263,20 @@ match_saturation_add (gimple_stmt_iterator *gsi, gphi > *phi) > * _3 = _4 - _5; > * _6 = _1 ? _3 : 0; > * => > - * _6 = .SAT_SUB (_4, _5); */ > + * _6 = .SAT_SUB (_4, _5); > + * Return true if the statement was replaced. */ > > -static void > +static bool > match_unsigned_saturation_sub (gimple_stmt_iterator *gsi, gassign *stmt) > { > tree ops[2]; > tree lhs = gimple_assign_lhs (stmt); > > if (gimple_unsigned_integer_sat_sub (lhs, ops, NULL)) > - build_saturation_binary_arith_call_and_replace (gsi, IFN_SAT_SUB, lhs, > - ops[0], ops[1]); > + return build_saturation_binary_arith_call_and_replace (gsi, IFN_SAT_SUB, > + lhs, ops[0], > ops[1]); > + > + return false; > } > > /* > @@ -7335,11 +7344,14 @@ math_opts_dom_walker::after_dom_children (basic_block > bb) > break; > > case PLUS_EXPR: > - match_saturation_add_with_assign (&gsi, as_a<gassign *> (stmt)); > - match_unsigned_saturation_sub (&gsi, as_a<gassign *> (stmt)); > + if (match_saturation_add_with_assign (&gsi, > + as_a<gassign *> (stmt))) > + break; > /* fall-through */ > case MINUS_EXPR: > - if (!convert_plusminus_to_widen (&gsi, stmt, code)) > + if (!match_unsigned_saturation_sub (&gsi, > + as_a<gassign *> (stmt)) > + && !convert_plusminus_to_widen (&gsi, stmt, code)) > { > match_arith_overflow (&gsi, stmt, code, m_cfg_changed_p); > if (gsi_stmt (gsi) == stmt) > -- > 2.50.1 (Apple Git-155) >
