diff --git a/gcc/match-sat-alu.pd b/gcc/match-sat-alu.pd
index 7156529f589..1b35b35cff8 100644
--- a/gcc/match-sat-alu.pd
+++ b/gcc/match-sat-alu.pd
@@ -131,6 +131,12 @@ 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))))
+ (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..029c136b7d0
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/sat_u_sub_minmax-3.c
@@ -0,0 +1,27 @@
+/* { 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;
+}
+
+u32
+add_min_live (u32 a, u32 b, u32 *o)
+{
+  u32 t = ~a;
+  u32 m = b < t ? b : t;
+  *o = m;
+  return a + m;
+}
+
+/* { dg-final { scan-tree-dump-not "\\.SAT_SUB " "optimized" } } */
+/* { dg-final { scan-tree-dump-not "\\.SAT_ADD " "optimized" } } */
diff --git a/gcc/tree-ssa-math-opts.cc b/gcc/tree-ssa-math-opts.cc
index b371b5b7cff..ed3abb7d5c4 100644
--- a/gcc/tree-ssa-math-opts.cc
+++ b/gcc/tree-ssa-math-opts.cc
@@ -7332,10 +7332,11 @@ math_opts_dom_walker::after_dom_children (basic_block bb)
 
 	    case PLUS_EXPR:
 	      match_saturation_add_with_assign (&gsi, as_a<gassign *> (stmt));
-	      match_unsigned_saturation_sub (&gsi, as_a<gassign *> (stmt));
 	      /* fall-through  */
 	    case MINUS_EXPR:
-	      if (!convert_plusminus_to_widen (&gsi, stmt, code))
+	      match_unsigned_saturation_sub (&gsi, as_a<gassign *> (stmt));
+	      if (gsi_stmt (gsi) == stmt
+		  && !convert_plusminus_to_widen (&gsi, stmt, code))
 		{
 		  match_arith_overflow (&gsi, stmt, code, m_cfg_changed_p);
 		  if (gsi_stmt (gsi) == stmt)
