diff mbox series

[11/13] match-sat-alu.pd: Recognize signed constant-clamp truncation

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

Commit Message

Kyrylo Tkachov Sept. 2, 2026, 2:52 p.m. UTC
From: Kyrylo Tkachov <ktkachov@nvidia.com>

A signed value clamped to the bounds of a narrower signed type is signed
saturating truncation.  Recognize both nesting orders of the MIN and MAX
operations.

Require exact bounds at the input precision, signed input and output types,
and a narrowing conversion.

With AArch64 -O2 -march=armv8-a -fopenmp-simd:

before:

clamp_i16_4:
	ldr	q0, [x1]
	movi	v30.4s, 0x7f, msl 8
	mvni	v31.4s, 0x7f, msl 8
	smin	v30.4s, v0.4s, v30.4s
	smax	v30.4s, v30.4s, v31.4s
	xtn	v30.4h, v30.4s
	str	d30, [x0]
	ret

after:

clamp_i16_4:
	ldr	q31, [x1]
	sqxtn	v31.4h, v31.4s
	str	d31, [x0]
	ret

Runtime tests cover both clamp orders, two widths, and LTO.  Target tests
cover valid forms and inexact bounds.

Bootstrapped and tested on aarch64-none-linux-gnu.

Ok for trunk?

gcc/ChangeLog:

	* match-sat-alu.pd (signed_integer_sat_trunc): Add the constant-clamp
	form.

gcc/testsuite/ChangeLog:

	* gcc.dg/vect/vect-sat-trunc-clamp-2.c: New test.
	* gcc.target/aarch64/vect-sat-trunc-clamp-s-1.c: Likewise.
	* gcc.target/aarch64/vect-sat-trunc-clamp-s-negative-1.c: Likewise.

Signed-off-by: Kyrylo Tkachov <ktkachov@nvidia.com>
---
 gcc/match-sat-alu.pd                          |  20 ++-
 .../gcc.dg/vect/vect-sat-trunc-clamp-2.c      | 126 ++++++++++++++++++
 .../aarch64/vect-sat-trunc-clamp-s-1.c        |   7 +
 .../vect-sat-trunc-clamp-s-negative-1.c       |  30 +++++
 4 files changed, 182 insertions(+), 1 deletion(-)
 create mode 100644 gcc/testsuite/gcc.dg/vect/vect-sat-trunc-clamp-2.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/vect-sat-trunc-clamp-s-1.c
 create mode 100644 gcc/testsuite/gcc.target/aarch64/vect-sat-trunc-clamp-s-negative-1.c
diff mbox series

Patch

diff --git a/gcc/match-sat-alu.pd b/gcc/match-sat-alu.pd
index ede949a34fe..ec58d86486d 100644
--- a/gcc/match-sat-alu.pd
+++ b/gcc/match-sat-alu.pd
@@ -665,7 +665,25 @@  along with GCC; see the file COPYING3.  If not see
 	     && wi::eq_p (int_cst_2, limit_2))
 	 || (wi::eq_p (int_cst_1, offset) && wi::eq_p (int_cst_2, limit_2))
 	 || (wi::eq_p (int_cst_1, otype_max_in_range)
-	     && wi::eq_p (int_cst_2, limit_1))))))))
+	     && wi::eq_p (int_cst_2, limit_1)))))))
+ (match (signed_integer_sat_trunc @0)
+  /* SAT_S_TRUNC = (NT)MAX (MIN (X, NT_MAX), NT_MIN).  */
+  (convert (integer_constant_clamp @0 @1 @2))
+  (if (!TYPE_UNSIGNED (TREE_TYPE (@0)))
+   (with
+    {
+     unsigned itype_precision = TYPE_PRECISION (TREE_TYPE (@0));
+     unsigned otype_precision = TYPE_PRECISION (type);
+     wide_int otype_min = wi::mask (otype_precision - 1, true,
+				    itype_precision);
+     wide_int otype_max = wi::mask (otype_precision - 1, false,
+				    itype_precision);
+     wide_int lo_cst = wi::to_wide (@1, itype_precision);
+     wide_int hi_cst = wi::to_wide (@2, itype_precision);
+    }
+    (if (otype_precision < itype_precision
+	 && wi::eq_p (lo_cst, otype_min)
+	 && wi::eq_p (hi_cst, otype_max)))))))
 
 /* An arithmetic right shift preserves the sign of its signed input.  The
    saturation value can therefore use the input sign while the range check
diff --git a/gcc/testsuite/gcc.dg/vect/vect-sat-trunc-clamp-2.c b/gcc/testsuite/gcc.dg/vect/vect-sat-trunc-clamp-2.c
new file mode 100644
index 00000000000..193979587b8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/vect/vect-sat-trunc-clamp-2.c
@@ -0,0 +1,126 @@ 
+/* { dg-do run } */
+/* { dg-require-effective-target vect_int } */
+/* { dg-additional-options "-O3 -fvect-cost-model=unlimited" } */
+
+#include "tree-vect.h"
+
+typedef __UINT32_TYPE__ u32;
+typedef __UINT64_TYPE__ u64;
+typedef __INT16_TYPE__ i16;
+typedef __INT32_TYPE__ i32;
+typedef __INT64_TYPE__ i64;
+
+#define N 259
+
+static i32 in32[N];
+static i64 in64[N];
+static i16 out16[N];
+static i32 out32[N];
+
+__attribute__((noipa)) static void
+clamp_s16_lo (i16 *__restrict out, const i32 *__restrict in, int n)
+{
+  for (int i = 0; i < n; ++i)
+    {
+      i32 x = in[i];
+      out[i] = x < -32768 ? -32768 : (x > 32767 ? 32767 : x);
+    }
+}
+
+__attribute__((noipa)) static void
+clamp_s16_hi (i16 *__restrict out, const i32 *__restrict in, int n)
+{
+  for (int i = 0; i < n; ++i)
+    {
+      i32 x = in[i];
+      out[i] = x > 32767 ? 32767 : (x < -32768 ? -32768 : x);
+    }
+}
+
+__attribute__((noipa)) static void
+clamp_s32_lo (i32 *__restrict out, const i64 *__restrict in, int n)
+{
+  for (int i = 0; i < n; ++i)
+    {
+      i64 x = in[i];
+      out[i] = (x < -2147483647LL - 1
+		? -2147483647LL - 1 : (x > 2147483647LL
+				      ? 2147483647LL : x));
+    }
+}
+
+__attribute__((noipa)) static void
+clamp_s32_hi (i32 *__restrict out, const i64 *__restrict in, int n)
+{
+  for (int i = 0; i < n; ++i)
+    {
+      i64 x = in[i];
+      out[i] = (x > 2147483647LL
+		? 2147483647LL : (x < -2147483647LL - 1
+				   ? -2147483647LL - 1 : x));
+    }
+}
+
+static i16
+ref_s16 (i32 x)
+{
+  return x < -32768 ? -32768 : (x > 32767 ? 32767 : x);
+}
+
+static i32
+ref_s32 (i64 x)
+{
+  return (x < -2147483647LL - 1
+	  ? -2147483647LL - 1 : (x > 2147483647LL ? 2147483647LL : x));
+}
+
+static void
+check_s16 (void)
+{
+#pragma GCC novector
+  for (int i = 0; i < N; ++i)
+    if (out16[i] != ref_s16 (in32[i]))
+      __builtin_abort ();
+}
+
+static void
+check_s32 (void)
+{
+#pragma GCC novector
+  for (int i = 0; i < N; ++i)
+    if (out32[i] != ref_s32 (in64[i]))
+      __builtin_abort ();
+}
+
+int
+main (void)
+{
+  check_vect ();
+
+  for (int i = 0; i < N; ++i)
+    {
+      in32[i] = (i32) ((u32) i * 2654435761U + 1013904223U);
+      in64[i] = (i64) ((u64) i * 11400714819323198485ULL
+			     + 13787848793156543929ULL);
+    }
+
+  in32[0] = -32769;
+  in32[1] = -32768;
+  in32[2] = 32767;
+  in32[3] = 32768;
+  in64[0] = -2147483647LL - 2;
+  in64[1] = -2147483647LL - 1;
+  in64[2] = 2147483647LL;
+  in64[3] = 2147483648LL;
+
+  clamp_s16_lo (out16, in32, N);
+  check_s16 ();
+  clamp_s16_hi (out16, in32, N);
+  check_s16 ();
+  clamp_s32_lo (out32, in64, N);
+  check_s32 ();
+  clamp_s32_hi (out32, in64, N);
+  check_s32 ();
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/aarch64/vect-sat-trunc-clamp-s-1.c b/gcc/testsuite/gcc.target/aarch64/vect-sat-trunc-clamp-s-1.c
new file mode 100644
index 00000000000..3a603ca18ef
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/vect-sat-trunc-clamp-s-1.c
@@ -0,0 +1,7 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O3 -march=armv8-a" } */
+/* { dg-additional-options "-mmax-vectorization --param=vect-epilogues-nomask=0 -fdump-tree-vect-details" } */
+
+#include "../../gcc.dg/vect/vect-sat-trunc-clamp-2.c"
+
+/* { dg-final { scan-tree-dump-times "sat_trunc pattern recognized" 4 "vect" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/vect-sat-trunc-clamp-s-negative-1.c b/gcc/testsuite/gcc.target/aarch64/vect-sat-trunc-clamp-s-negative-1.c
new file mode 100644
index 00000000000..b0f86653554
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/vect-sat-trunc-clamp-s-negative-1.c
@@ -0,0 +1,30 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O3 -march=armv8-a" } */
+/* { dg-additional-options "-mmax-vectorization --param=vect-epilogues-nomask=0 -fdump-tree-vect-details" } */
+
+typedef __INT16_TYPE__ i16;
+typedef __INT32_TYPE__ i32;
+
+__attribute__((noipa))
+void
+bad_low (i16 *__restrict out, const i32 *__restrict in, int n)
+{
+  for (int i = 0; i < n; ++i)
+    {
+      i32 x = in[i];
+      out[i] = x < -32767 ? -32767 : (x > 32767 ? 32767 : x);
+    }
+}
+
+__attribute__((noipa))
+void
+bad_high (i16 *__restrict out, const i32 *__restrict in, int n)
+{
+  for (int i = 0; i < n; ++i)
+    {
+      i32 x = in[i];
+      out[i] = x > 32766 ? 32766 : (x < -32768 ? -32768 : x);
+    }
+}
+
+/* { dg-final { scan-tree-dump-not "sat_trunc pattern recognized" "vect" } } */