[clang] c00db97 - [Clang] Add elementwise saturated add/sub builtins
Simon Pilgrim via cfe-commits
cfe-commits at lists.llvm.org
Tue Feb 8 03:22:09 PST 2022
Author: Simon Pilgrim
Date: 2022-02-08T11:22:01Z
New Revision: c00db971597557e952901eac4be131157d1d5eb9
URL: https://github.com/llvm/llvm-project/commit/c00db971597557e952901eac4be131157d1d5eb9
DIFF: https://github.com/llvm/llvm-project/commit/c00db971597557e952901eac4be131157d1d5eb9.diff
LOG: [Clang] Add elementwise saturated add/sub builtins
This patch implements `__builtin_elementwise_add_sat` and `__builtin_elementwise_sub_sat` builtins.
These map to the add/sub saturated math intrinsics described here:
https://llvm.org/docs/LangRef.html#saturation-arithmetic-intrinsics
With this in place we should then be able to replace the x86 SSE adds/subs intrinsics with these generic variants - it looks like other targets should be able to use these as well (arm/aarch64/webassembly all have similar examples in cgbuiltin).
Differential Revision: https://reviews.llvm.org/D117898
Added:
Modified:
clang/docs/LanguageExtensions.rst
clang/include/clang/Basic/Builtins.def
clang/lib/CodeGen/CGBuiltin.cpp
clang/lib/Sema/SemaChecking.cpp
clang/test/CodeGen/builtins-elementwise-math.c
clang/test/Sema/builtins-elementwise-math.c
clang/test/SemaCXX/builtins-elementwise-math.cpp
Removed:
################################################################################
diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index e74539d650ce0..35a964727bed1 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -545,6 +545,10 @@ Unless specified otherwise operation(±0) = ±0 and operation(±infinity) = ±in
magnitude than x
T __builtin_elementwise_max(T x, T y) return x or y, whichever is larger integer and floating point types
T __builtin_elementwise_min(T x, T y) return x or y, whichever is smaller integer and floating point types
+ T __builtin_elementwise_add_sat(T x, T y) return the sum of x and y, clamped to the range of integer types
+ representable values for the signed/unsigned integer type.
+ T __builtin_elementwise_sub_sat(T x, T y) return the
diff erence of x and y, clamped to the range of integer types
+ representable values for the signed/unsigned integer type..
========================================= ================================================================ =========================================
diff --git a/clang/include/clang/Basic/Builtins.def b/clang/include/clang/Basic/Builtins.def
index d2cb14d2fd8cc..66c20e1c691de 100644
--- a/clang/include/clang/Basic/Builtins.def
+++ b/clang/include/clang/Basic/Builtins.def
@@ -653,6 +653,8 @@ BUILTIN(__builtin_elementwise_ceil, "v.", "nct")
BUILTIN(__builtin_elementwise_floor, "v.", "nct")
BUILTIN(__builtin_elementwise_roundeven, "v.", "nct")
BUILTIN(__builtin_elementwise_trunc, "v.", "nct")
+BUILTIN(__builtin_elementwise_add_sat, "v.", "nct")
+BUILTIN(__builtin_elementwise_sub_sat, "v.", "nct")
BUILTIN(__builtin_reduce_max, "v.", "nct")
BUILTIN(__builtin_reduce_min, "v.", "nct")
BUILTIN(__builtin_reduce_xor, "v.", "nct")
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index 7ec9994aea41a..efb327e6d7707 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -3154,6 +3154,25 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
return RValue::get(
emitUnaryBuiltin(*this, E, llvm::Intrinsic::trunc, "elt.trunc"));
+ case Builtin::BI__builtin_elementwise_add_sat:
+ case Builtin::BI__builtin_elementwise_sub_sat: {
+ Value *Op0 = EmitScalarExpr(E->getArg(0));
+ Value *Op1 = EmitScalarExpr(E->getArg(1));
+ Value *Result;
+ assert(Op0->getType()->isIntOrIntVectorTy() && "integer type expected");
+ QualType Ty = E->getArg(0)->getType();
+ if (auto *VecTy = Ty->getAs<VectorType>())
+ Ty = VecTy->getElementType();
+ bool IsSigned = Ty->isSignedIntegerType();
+ unsigned Opc;
+ if (BuiltinIDIfNoAsmLabel == Builtin::BI__builtin_elementwise_add_sat)
+ Opc = IsSigned ? llvm::Intrinsic::sadd_sat : llvm::Intrinsic::uadd_sat;
+ else
+ Opc = IsSigned ? llvm::Intrinsic::ssub_sat : llvm::Intrinsic::usub_sat;
+ Result = Builder.CreateBinaryIntrinsic(Opc, Op0, Op1, nullptr, "elt.sat");
+ return RValue::get(Result);
+ }
+
case Builtin::BI__builtin_elementwise_max: {
Value *Op0 = EmitScalarExpr(E->getArg(0));
Value *Op1 = EmitScalarExpr(E->getArg(1));
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index dfbf4cdc89cb9..fe704792ce0b0 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -2238,6 +2238,28 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
break;
}
+ // These builtins restrict the element type to integer
+ // types only.
+ case Builtin::BI__builtin_elementwise_add_sat:
+ case Builtin::BI__builtin_elementwise_sub_sat: {
+ if (SemaBuiltinElementwiseMath(TheCall))
+ return ExprError();
+
+ const Expr *Arg = TheCall->getArg(0);
+ QualType ArgTy = Arg->getType();
+ QualType EltTy = ArgTy;
+
+ if (auto *VecTy = EltTy->getAs<VectorType>())
+ EltTy = VecTy->getElementType();
+
+ if (!EltTy->isIntegerType()) {
+ Diag(Arg->getBeginLoc(), diag::err_builtin_invalid_arg_type)
+ << 1 << /* integer ty */ 6 << ArgTy;
+ return ExprError();
+ }
+ break;
+ }
+
case Builtin::BI__builtin_elementwise_min:
case Builtin::BI__builtin_elementwise_max:
if (SemaBuiltinElementwiseMath(TheCall))
diff --git a/clang/test/CodeGen/builtins-elementwise-math.c b/clang/test/CodeGen/builtins-elementwise-math.c
index 6a786432aed8c..3e4ab3c2fd973 100644
--- a/clang/test/CodeGen/builtins-elementwise-math.c
+++ b/clang/test/CodeGen/builtins-elementwise-math.c
@@ -56,6 +56,104 @@ void test_builtin_elementwise_abs(float f1, float f2, double d1, double d2,
si = __builtin_elementwise_abs(si);
}
+void test_builtin_elementwise_add_sat(float f1, float f2, double d1, double d2,
+ float4 vf1, float4 vf2, long long int i1,
+ long long int i2, si8 vi1, si8 vi2,
+ unsigned u1, unsigned u2, u4 vu1, u4 vu2,
+ _BitInt(31) bi1, _BitInt(31) bi2,
+ unsigned _BitInt(55) bu1, unsigned _BitInt(55) bu2) {
+ // CHECK: [[I1:%.+]] = load i64, i64* %i1.addr, align 8
+ // CHECK-NEXT: [[I2:%.+]] = load i64, i64* %i2.addr, align 8
+ // CHECK-NEXT: call i64 @llvm.sadd.sat.i64(i64 [[I1]], i64 [[I2]])
+ i1 = __builtin_elementwise_add_sat(i1, i2);
+
+ // CHECK: [[I1:%.+]] = load i64, i64* %i1.addr, align 8
+ // CHECK-NEXT: call i64 @llvm.sadd.sat.i64(i64 [[I1]], i64 10)
+ i1 = __builtin_elementwise_add_sat(i1, 10);
+
+ // CHECK: [[VI1:%.+]] = load <8 x i16>, <8 x i16>* %vi1.addr, align 16
+ // CHECK-NEXT: [[VI2:%.+]] = load <8 x i16>, <8 x i16>* %vi2.addr, align 16
+ // CHECK-NEXT: call <8 x i16> @llvm.sadd.sat.v8i16(<8 x i16> [[VI1]], <8 x i16> [[VI2]])
+ vi1 = __builtin_elementwise_add_sat(vi1, vi2);
+
+ // CHECK: [[U1:%.+]] = load i32, i32* %u1.addr, align 4
+ // CHECK-NEXT: [[U2:%.+]] = load i32, i32* %u2.addr, align 4
+ // CHECK-NEXT: call i32 @llvm.uadd.sat.i32(i32 [[U1]], i32 [[U2]])
+ u1 = __builtin_elementwise_add_sat(u1, u2);
+
+ // CHECK: [[VU1:%.+]] = load <4 x i32>, <4 x i32>* %vu1.addr, align 16
+ // CHECK-NEXT: [[VU2:%.+]] = load <4 x i32>, <4 x i32>* %vu2.addr, align 16
+ // CHECK-NEXT: call <4 x i32> @llvm.uadd.sat.v4i32(<4 x i32> [[VU1]], <4 x i32> [[VU2]])
+ vu1 = __builtin_elementwise_add_sat(vu1, vu2);
+
+ // CHECK: [[BI1:%.+]] = load i31, i31* %bi1.addr, align 4
+ // CHECK-NEXT: [[BI2:%.+]] = load i31, i31* %bi2.addr, align 4
+ // CHECK-NEXT: call i31 @llvm.sadd.sat.i31(i31 [[BI1]], i31 [[BI2]])
+ bi1 = __builtin_elementwise_add_sat(bi1, bi2);
+
+ // CHECK: [[BU1:%.+]] = load i55, i55* %bu1.addr, align 8
+ // CHECK-NEXT: [[BU2:%.+]] = load i55, i55* %bu2.addr, align 8
+ // CHECK-NEXT: call i55 @llvm.uadd.sat.i55(i55 [[BU1]], i55 [[BU2]])
+ bu1 = __builtin_elementwise_add_sat(bu1, bu2);
+
+ // CHECK: [[IAS1:%.+]] = load i32, i32 addrspace(1)* @int_as_one, align 4
+ // CHECK-NEXT: [[B:%.+]] = load i32, i32* @b, align 4
+ // CHECK-NEXT: call i32 @llvm.sadd.sat.i32(i32 [[IAS1]], i32 [[B]])
+ int_as_one = __builtin_elementwise_add_sat(int_as_one, b);
+
+ // CHECK: call i32 @llvm.sadd.sat.i32(i32 1, i32 97)
+ i1 = __builtin_elementwise_add_sat(1, 'a');
+}
+
+void test_builtin_elementwise_sub_sat(float f1, float f2, double d1, double d2,
+ float4 vf1, float4 vf2, long long int i1,
+ long long int i2, si8 vi1, si8 vi2,
+ unsigned u1, unsigned u2, u4 vu1, u4 vu2,
+ _BitInt(31) bi1, _BitInt(31) bi2,
+ unsigned _BitInt(55) bu1, unsigned _BitInt(55) bu2) {
+ // CHECK: [[I1:%.+]] = load i64, i64* %i1.addr, align 8
+ // CHECK-NEXT: [[I2:%.+]] = load i64, i64* %i2.addr, align 8
+ // CHECK-NEXT: call i64 @llvm.ssub.sat.i64(i64 [[I1]], i64 [[I2]])
+ i1 = __builtin_elementwise_sub_sat(i1, i2);
+
+ // CHECK: [[I1:%.+]] = load i64, i64* %i1.addr, align 8
+ // CHECK-NEXT: call i64 @llvm.ssub.sat.i64(i64 [[I1]], i64 10)
+ i1 = __builtin_elementwise_sub_sat(i1, 10);
+
+ // CHECK: [[VI1:%.+]] = load <8 x i16>, <8 x i16>* %vi1.addr, align 16
+ // CHECK-NEXT: [[VI2:%.+]] = load <8 x i16>, <8 x i16>* %vi2.addr, align 16
+ // CHECK-NEXT: call <8 x i16> @llvm.ssub.sat.v8i16(<8 x i16> [[VI1]], <8 x i16> [[VI2]])
+ vi1 = __builtin_elementwise_sub_sat(vi1, vi2);
+
+ // CHECK: [[U1:%.+]] = load i32, i32* %u1.addr, align 4
+ // CHECK-NEXT: [[U2:%.+]] = load i32, i32* %u2.addr, align 4
+ // CHECK-NEXT: call i32 @llvm.usub.sat.i32(i32 [[U1]], i32 [[U2]])
+ u1 = __builtin_elementwise_sub_sat(u1, u2);
+
+ // CHECK: [[VU1:%.+]] = load <4 x i32>, <4 x i32>* %vu1.addr, align 16
+ // CHECK-NEXT: [[VU2:%.+]] = load <4 x i32>, <4 x i32>* %vu2.addr, align 16
+ // CHECK-NEXT: call <4 x i32> @llvm.usub.sat.v4i32(<4 x i32> [[VU1]], <4 x i32> [[VU2]])
+ vu1 = __builtin_elementwise_sub_sat(vu1, vu2);
+
+ // CHECK: [[BI1:%.+]] = load i31, i31* %bi1.addr, align 4
+ // CHECK-NEXT: [[BI2:%.+]] = load i31, i31* %bi2.addr, align 4
+ // CHECK-NEXT: call i31 @llvm.ssub.sat.i31(i31 [[BI1]], i31 [[BI2]])
+ bi1 = __builtin_elementwise_sub_sat(bi1, bi2);
+
+ // CHECK: [[BU1:%.+]] = load i55, i55* %bu1.addr, align 8
+ // CHECK-NEXT: [[BU2:%.+]] = load i55, i55* %bu2.addr, align 8
+ // CHECK-NEXT: call i55 @llvm.usub.sat.i55(i55 [[BU1]], i55 [[BU2]])
+ bu1 = __builtin_elementwise_sub_sat(bu1, bu2);
+
+ // CHECK: [[IAS1:%.+]] = load i32, i32 addrspace(1)* @int_as_one, align 4
+ // CHECK-NEXT: [[B:%.+]] = load i32, i32* @b, align 4
+ // CHECK-NEXT: call i32 @llvm.ssub.sat.i32(i32 [[IAS1]], i32 [[B]])
+ int_as_one = __builtin_elementwise_sub_sat(int_as_one, b);
+
+ // CHECK: call i32 @llvm.ssub.sat.i32(i32 1, i32 97)
+ i1 = __builtin_elementwise_sub_sat(1, 'a');
+}
+
void test_builtin_elementwise_max(float f1, float f2, double d1, double d2,
float4 vf1, float4 vf2, long long int i1,
long long int i2, si8 vi1, si8 vi2,
diff --git a/clang/test/Sema/builtins-elementwise-math.c b/clang/test/Sema/builtins-elementwise-math.c
index f2da7a0327259..273cdddcb1238 100644
--- a/clang/test/Sema/builtins-elementwise-math.c
+++ b/clang/test/Sema/builtins-elementwise-math.c
@@ -33,6 +33,122 @@ void test_builtin_elementwise_abs(int i, double d, float4 v, int3 iv, unsigned u
// expected-error at -1 {{1st argument must be a signed integer or floating point type (was 'unsigned4' (vector of 4 'unsigned int' values))}}
}
+void test_builtin_elementwise_add_sat(int i, short s, double d, float4 v, int3 iv, unsigned3 uv, int *p) {
+ i = __builtin_elementwise_add_sat(p, d);
+ // expected-error at -1 {{arguments are of
diff erent types ('int *' vs 'double')}}
+
+ struct Foo foo = __builtin_elementwise_add_sat(i, i);
+ // expected-error at -1 {{initializing 'struct Foo' with an expression of incompatible type 'int'}}
+
+ i = __builtin_elementwise_add_sat(i);
+ // expected-error at -1 {{too few arguments to function call, expected 2, have 1}}
+
+ i = __builtin_elementwise_add_sat();
+ // expected-error at -1 {{too few arguments to function call, expected 2, have 0}}
+
+ i = __builtin_elementwise_add_sat(i, i, i);
+ // expected-error at -1 {{too many arguments to function call, expected 2, have 3}}
+
+ i = __builtin_elementwise_add_sat(v, iv);
+ // expected-error at -1 {{arguments are of
diff erent types ('float4' (vector of 4 'float' values) vs 'int3' (vector of 3 'int' values))}}
+
+ i = __builtin_elementwise_add_sat(uv, iv);
+ // expected-error at -1 {{arguments are of
diff erent types ('unsigned3' (vector of 3 'unsigned int' values) vs 'int3' (vector of 3 'int' values))}}
+
+ v = __builtin_elementwise_add_sat(v, v);
+ // expected-error at -1 {{1st argument must be a vector of integers (was 'float4' (vector of 4 'float' values))}}
+
+ s = __builtin_elementwise_add_sat(i, s);
+
+ enum e { one,
+ two };
+ i = __builtin_elementwise_add_sat(one, two);
+
+ enum f { three };
+ enum f x = __builtin_elementwise_add_sat(one, three);
+
+ _BitInt(32) ext; // expected-warning {{'_BitInt' in C17 and earlier is a Clang extension}}
+ ext = __builtin_elementwise_add_sat(ext, ext);
+
+ const int ci;
+ i = __builtin_elementwise_add_sat(ci, i);
+ i = __builtin_elementwise_add_sat(i, ci);
+ i = __builtin_elementwise_add_sat(ci, ci);
+
+ i = __builtin_elementwise_add_sat(i, int_as_one); // ok (attributes don't match)?
+ i = __builtin_elementwise_add_sat(i, b); // ok (sugar doesn't match)?
+
+ int A[10];
+ A = __builtin_elementwise_add_sat(A, A);
+ // expected-error at -1 {{1st argument must be a vector, integer or floating point type (was 'int *')}}
+
+ int(ii);
+ int j;
+ j = __builtin_elementwise_add_sat(i, j);
+
+ _Complex float c1, c2;
+ c1 = __builtin_elementwise_add_sat(c1, c2);
+ // expected-error at -1 {{1st argument must be a vector, integer or floating point type (was '_Complex float')}}
+}
+
+void test_builtin_elementwise_sub_sat(int i, short s, double d, float4 v, int3 iv, unsigned3 uv, int *p) {
+ i = __builtin_elementwise_sub_sat(p, d);
+ // expected-error at -1 {{arguments are of
diff erent types ('int *' vs 'double')}}
+
+ struct Foo foo = __builtin_elementwise_sub_sat(i, i);
+ // expected-error at -1 {{initializing 'struct Foo' with an expression of incompatible type 'int'}}
+
+ i = __builtin_elementwise_sub_sat(i);
+ // expected-error at -1 {{too few arguments to function call, expected 2, have 1}}
+
+ i = __builtin_elementwise_sub_sat();
+ // expected-error at -1 {{too few arguments to function call, expected 2, have 0}}
+
+ i = __builtin_elementwise_sub_sat(i, i, i);
+ // expected-error at -1 {{too many arguments to function call, expected 2, have 3}}
+
+ i = __builtin_elementwise_sub_sat(v, iv);
+ // expected-error at -1 {{arguments are of
diff erent types ('float4' (vector of 4 'float' values) vs 'int3' (vector of 3 'int' values))}}
+
+ i = __builtin_elementwise_sub_sat(uv, iv);
+ // expected-error at -1 {{arguments are of
diff erent types ('unsigned3' (vector of 3 'unsigned int' values) vs 'int3' (vector of 3 'int' values))}}
+
+ v = __builtin_elementwise_sub_sat(v, v);
+ // expected-error at -1 {{1st argument must be a vector of integers (was 'float4' (vector of 4 'float' values))}}
+
+ s = __builtin_elementwise_sub_sat(i, s);
+
+ enum e { one,
+ two };
+ i = __builtin_elementwise_sub_sat(one, two);
+
+ enum f { three };
+ enum f x = __builtin_elementwise_sub_sat(one, three);
+
+ _BitInt(32) ext; // expected-warning {{'_BitInt' in C17 and earlier is a Clang extension}}
+ ext = __builtin_elementwise_sub_sat(ext, ext);
+
+ const int ci;
+ i = __builtin_elementwise_sub_sat(ci, i);
+ i = __builtin_elementwise_sub_sat(i, ci);
+ i = __builtin_elementwise_sub_sat(ci, ci);
+
+ i = __builtin_elementwise_sub_sat(i, int_as_one); // ok (attributes don't match)?
+ i = __builtin_elementwise_sub_sat(i, b); // ok (sugar doesn't match)?
+
+ int A[10];
+ A = __builtin_elementwise_sub_sat(A, A);
+ // expected-error at -1 {{1st argument must be a vector, integer or floating point type (was 'int *')}}
+
+ int(ii);
+ int j;
+ j = __builtin_elementwise_sub_sat(i, j);
+
+ _Complex float c1, c2;
+ c1 = __builtin_elementwise_sub_sat(c1, c2);
+ // expected-error at -1 {{1st argument must be a vector, integer or floating point type (was '_Complex float')}}
+}
+
void test_builtin_elementwise_max(int i, short s, double d, float4 v, int3 iv, unsigned3 uv, int *p) {
i = __builtin_elementwise_max(p, d);
// expected-error at -1 {{arguments are of
diff erent types ('int *' vs 'double')}}
diff --git a/clang/test/SemaCXX/builtins-elementwise-math.cpp b/clang/test/SemaCXX/builtins-elementwise-math.cpp
index c40095ab0d53b..96234e16c3a0e 100644
--- a/clang/test/SemaCXX/builtins-elementwise-math.cpp
+++ b/clang/test/SemaCXX/builtins-elementwise-math.cpp
@@ -21,6 +21,22 @@ void test_builtin_elementwise_abs() {
static_assert(!is_const<decltype(__builtin_elementwise_abs(b))>::value);
}
+void test_builtin_elementwise_add_sat() {
+ const int a = 2;
+ int b = 1;
+ static_assert(!is_const<decltype(__builtin_elementwise_add_sat(a, b))>::value);
+ static_assert(!is_const<decltype(__builtin_elementwise_add_sat(b, a))>::value);
+ static_assert(!is_const<decltype(__builtin_elementwise_add_sat(a, a))>::value);
+}
+
+void test_builtin_elementwise_sub_sat() {
+ const int a = 2;
+ int b = 1;
+ static_assert(!is_const<decltype(__builtin_elementwise_sub_sat(a, b))>::value);
+ static_assert(!is_const<decltype(__builtin_elementwise_sub_sat(b, a))>::value);
+ static_assert(!is_const<decltype(__builtin_elementwise_sub_sat(a, a))>::value);
+}
+
void test_builtin_elementwise_max() {
const int a = 2;
int b = 1;
More information about the cfe-commits
mailing list