diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst index e739ecf3b9df4..3d2e8aae4421c 100644 --- a/clang/docs/LanguageExtensions.rst +++ b/clang/docs/LanguageExtensions.rst @@ -3516,18 +3516,15 @@ Floating point builtins ``__builtin_isfpclass`` ----------------------- -``__builtin_isfpclass`` is used to test if the specified floating-point value -falls into one of the specified floating-point classes. +``__builtin_isfpclass`` is used to test if the specified floating-point values +fall into one of the specified floating-point classes. **Syntax**: .. code-block:: c++ int __builtin_isfpclass(fp_type expr, int mask) - -``fp_type`` is a floating-point type supported by the target. ``mask`` is an -integer constant expression, where each bit represents floating-point class to -test. The function returns boolean value. + int_vector __builtin_isfpclass(fp_vector expr, int mask) **Example of use**: @@ -3543,8 +3540,9 @@ test. The function returns boolean value. The ``__builtin_isfpclass()`` builtin is a generalization of functions ``isnan``, ``isinf``, ``isfinite`` and some others defined by the C standard. It tests if the floating-point value, specified by the first argument, falls into any of data -classes, specified by the second argument. The later is a bitmask, in which each -data class is represented by a bit using the encoding: +classes, specified by the second argument. The latter is an integer constant +bitmask expression, in which each data class is represented by a bit +using the encoding: ========== =================== ====================== Mask value Data class Macro @@ -3572,6 +3570,14 @@ the standard classification functions, for example, ``__builtin_isfpclass(x, 3)` is identical to ``isnan``,``__builtin_isfpclass(x, 504)`` - to ``isfinite`` and so on. +If the first argument is a vector, the function is equivalent to the set of +scalar calls of ``__builtin_isfpclass`` applied to the input elementwise. + +The result of ``__builtin_isfpclass`` is a boolean value, if the first argument +is a scalar, or an integer vector with the same element count as the first +argument. The element type in this vector has the same bit length as the +element of the the first argument type. + This function never raises floating-point exceptions and does not canonicalize its input. The floating-point argument is not promoted, its data class is determined based on its representation in its actual semantic type. diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 895cd43ed1e81..51a04735bfd66 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -345,6 +345,7 @@ Floating Point Support in Clang - Add ``__builtin_elementwise_pow`` builtin for floating point types only. - Add ``__builtin_elementwise_bitreverse`` builtin for integer types only. - Add ``__builtin_elementwise_sqrt`` builtin for floating point types only. +- ``__builtin_isfpclass`` builtin now supports vector types. AST Matchers ------------ diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index c78a6b9c51076..3932d9cd07d98 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -8404,14 +8404,15 @@ bool Sema::SemaBuiltinUnorderedCompare(CallExpr *TheCall) { /// SemaBuiltinSemaBuiltinFPClassification - Handle functions like /// __builtin_isnan and friends. This is declared to take (...), so we have -/// to check everything. We expect the last argument to be a floating point -/// value. +/// to check everything. bool Sema::SemaBuiltinFPClassification(CallExpr *TheCall, unsigned NumArgs) { if (checkArgCount(*this, TheCall, NumArgs)) return true; + bool IsFPClass = NumArgs == 2; + // Find out position of floating-point argument. - unsigned FPArgNo = (NumArgs == 2) ? 0 : NumArgs - 1; + unsigned FPArgNo = IsFPClass ? 0 : NumArgs - 1; // We can count on all parameters preceding the floating-point just being int. // Try all of those. @@ -8442,18 +8443,37 @@ bool Sema::SemaBuiltinFPClassification(CallExpr *TheCall, unsigned NumArgs) { OrigArg = DefaultFunctionArrayLvalueConversion(OrigArg).get(); TheCall->setArg(FPArgNo, OrigArg); + QualType VectorResultTy; + QualType ElementTy = OrigArg->getType(); + // TODO: When all classification function are implemented with is_fpclass, + // vector argument can be supported in all of them. + if (ElementTy->isVectorType() && IsFPClass) { + VectorResultTy = GetSignedVectorType(ElementTy); + ElementTy = ElementTy->getAs()->getElementType(); + } + // This operation requires a non-_Complex floating-point number. - if (!OrigArg->getType()->isRealFloatingType()) + if (!ElementTy->isRealFloatingType()) return Diag(OrigArg->getBeginLoc(), diag::err_typecheck_call_invalid_unary_fp) << OrigArg->getType() << OrigArg->getSourceRange(); // __builtin_isfpclass has integer parameter that specify test mask. It is // passed in (...), so it should be analyzed completely here. - if (NumArgs == 2) + if (IsFPClass) if (SemaBuiltinConstantArgRange(TheCall, 1, 0, llvm::fcAllFlags)) return true; + // TODO: enable this code to all classification functions. + if (IsFPClass) { + QualType ResultTy; + if (!VectorResultTy.isNull()) + ResultTy = VectorResultTy; + else + ResultTy = Context.IntTy; + TheCall->setType(ResultTy); + } + return false; } diff --git a/clang/test/CodeGen/isfpclass.c b/clang/test/CodeGen/isfpclass.c index 2ad5bb3188250..34873c08e04f8 100644 --- a/clang/test/CodeGen/isfpclass.c +++ b/clang/test/CodeGen/isfpclass.c @@ -15,7 +15,7 @@ _Bool check_isfpclass_finite(float x) { // CHECK-LABEL: define dso_local i1 @check_isfpclass_finite_strict // CHECK-SAME: (float noundef [[X:%.*]]) local_unnamed_addr #[[ATTR2:[0-9]+]] { // CHECK-NEXT: entry: -// CHECK-NEXT: [[TMP0:%.*]] = tail call i1 @llvm.is.fpclass.f32(float [[X]], i32 504) #[[ATTR5:[0-9]+]] +// CHECK-NEXT: [[TMP0:%.*]] = tail call i1 @llvm.is.fpclass.f32(float [[X]], i32 504) #[[ATTR6:[0-9]+]] // CHECK-NEXT: ret i1 [[TMP0]] // _Bool check_isfpclass_finite_strict(float x) { @@ -36,7 +36,7 @@ _Bool check_isfpclass_nan_f32(float x) { // CHECK-LABEL: define dso_local i1 @check_isfpclass_nan_f32_strict // CHECK-SAME: (float noundef [[X:%.*]]) local_unnamed_addr #[[ATTR2]] { // CHECK-NEXT: entry: -// CHECK-NEXT: [[TMP0:%.*]] = tail call i1 @llvm.is.fpclass.f32(float [[X]], i32 3) #[[ATTR5]] +// CHECK-NEXT: [[TMP0:%.*]] = tail call i1 @llvm.is.fpclass.f32(float [[X]], i32 3) #[[ATTR6]] // CHECK-NEXT: ret i1 [[TMP0]] // _Bool check_isfpclass_nan_f32_strict(float x) { @@ -57,7 +57,7 @@ _Bool check_isfpclass_snan_f64(double x) { // CHECK-LABEL: define dso_local i1 @check_isfpclass_snan_f64_strict // CHECK-SAME: (double noundef [[X:%.*]]) local_unnamed_addr #[[ATTR2]] { // CHECK-NEXT: entry: -// CHECK-NEXT: [[TMP0:%.*]] = tail call i1 @llvm.is.fpclass.f64(double [[X]], i32 1) #[[ATTR5]] +// CHECK-NEXT: [[TMP0:%.*]] = tail call i1 @llvm.is.fpclass.f64(double [[X]], i32 1) #[[ATTR6]] // CHECK-NEXT: ret i1 [[TMP0]] // _Bool check_isfpclass_snan_f64_strict(double x) { @@ -78,7 +78,7 @@ _Bool check_isfpclass_zero_f16(_Float16 x) { // CHECK-LABEL: define dso_local i1 @check_isfpclass_zero_f16_strict // CHECK-SAME: (half noundef [[X:%.*]]) local_unnamed_addr #[[ATTR2]] { // CHECK-NEXT: entry: -// CHECK-NEXT: [[TMP0:%.*]] = tail call i1 @llvm.is.fpclass.f16(half [[X]], i32 96) #[[ATTR5]] +// CHECK-NEXT: [[TMP0:%.*]] = tail call i1 @llvm.is.fpclass.f16(half [[X]], i32 96) #[[ATTR6]] // CHECK-NEXT: ret i1 [[TMP0]] // _Bool check_isfpclass_zero_f16_strict(_Float16 x) { @@ -89,7 +89,7 @@ _Bool check_isfpclass_zero_f16_strict(_Float16 x) { // CHECK-LABEL: define dso_local i1 @check_isnan // CHECK-SAME: (float noundef [[X:%.*]]) local_unnamed_addr #[[ATTR2]] { // CHECK-NEXT: entry: -// CHECK-NEXT: [[TMP0:%.*]] = tail call i1 @llvm.is.fpclass.f32(float [[X]], i32 3) #[[ATTR5]] +// CHECK-NEXT: [[TMP0:%.*]] = tail call i1 @llvm.is.fpclass.f32(float [[X]], i32 3) #[[ATTR6]] // CHECK-NEXT: ret i1 [[TMP0]] // _Bool check_isnan(float x) { @@ -100,7 +100,7 @@ _Bool check_isnan(float x) { // CHECK-LABEL: define dso_local i1 @check_isinf // CHECK-SAME: (float noundef [[X:%.*]]) local_unnamed_addr #[[ATTR2]] { // CHECK-NEXT: entry: -// CHECK-NEXT: [[TMP0:%.*]] = tail call i1 @llvm.is.fpclass.f32(float [[X]], i32 516) #[[ATTR5]] +// CHECK-NEXT: [[TMP0:%.*]] = tail call i1 @llvm.is.fpclass.f32(float [[X]], i32 516) #[[ATTR6]] // CHECK-NEXT: ret i1 [[TMP0]] // _Bool check_isinf(float x) { @@ -111,7 +111,7 @@ _Bool check_isinf(float x) { // CHECK-LABEL: define dso_local i1 @check_isfinite // CHECK-SAME: (float noundef [[X:%.*]]) local_unnamed_addr #[[ATTR2]] { // CHECK-NEXT: entry: -// CHECK-NEXT: [[TMP0:%.*]] = tail call i1 @llvm.is.fpclass.f32(float [[X]], i32 504) #[[ATTR5]] +// CHECK-NEXT: [[TMP0:%.*]] = tail call i1 @llvm.is.fpclass.f32(float [[X]], i32 504) #[[ATTR6]] // CHECK-NEXT: ret i1 [[TMP0]] // _Bool check_isfinite(float x) { @@ -122,7 +122,7 @@ _Bool check_isfinite(float x) { // CHECK-LABEL: define dso_local i1 @check_isnormal // CHECK-SAME: (float noundef [[X:%.*]]) local_unnamed_addr #[[ATTR2]] { // CHECK-NEXT: entry: -// CHECK-NEXT: [[TMP0:%.*]] = tail call i1 @llvm.is.fpclass.f32(float [[X]], i32 264) #[[ATTR5]] +// CHECK-NEXT: [[TMP0:%.*]] = tail call i1 @llvm.is.fpclass.f32(float [[X]], i32 264) #[[ATTR6]] // CHECK-NEXT: ret i1 [[TMP0]] // _Bool check_isnormal(float x) { @@ -130,3 +130,44 @@ _Bool check_isnormal(float x) { return __builtin_isnormal(x); } + +typedef float __attribute__((ext_vector_type(4))) float4; +typedef double __attribute__((ext_vector_type(4))) double4; +typedef int __attribute__((ext_vector_type(4))) int4; +typedef long __attribute__((ext_vector_type(4))) long4; + +// CHECK-LABEL: define dso_local <4 x i32> @check_isfpclass_nan_v4f32 +// CHECK-SAME: (<4 x float> noundef [[X:%.*]]) local_unnamed_addr #[[ATTR3]] { +// CHECK-NEXT: entry: +// CHECK-NEXT: [[TMP0:%.*]] = fcmp uno <4 x float> [[X]], zeroinitializer +// CHECK-NEXT: [[TMP1:%.*]] = zext <4 x i1> [[TMP0]] to <4 x i32> +// CHECK-NEXT: ret <4 x i32> [[TMP1]] +// +int4 check_isfpclass_nan_v4f32(float4 x) { + return __builtin_isfpclass(x, 3 /*NaN*/); +} + +// CHECK-LABEL: define dso_local <4 x i32> @check_isfpclass_nan_strict_v4f32 +// CHECK-SAME: (<4 x float> noundef [[X:%.*]]) local_unnamed_addr #[[ATTR2]] { +// CHECK-NEXT: entry: +// CHECK-NEXT: [[TMP0:%.*]] = tail call <4 x i1> @llvm.is.fpclass.v4f32(<4 x float> [[X]], i32 3) #[[ATTR6]] +// CHECK-NEXT: [[TMP1:%.*]] = zext <4 x i1> [[TMP0]] to <4 x i32> +// CHECK-NEXT: ret <4 x i32> [[TMP1]] +// +int4 check_isfpclass_nan_strict_v4f32(float4 x) { +#pragma STDC FENV_ACCESS ON + return __builtin_isfpclass(x, 3 /*NaN*/); +} + +// CHECK-LABEL: define dso_local void @check_isfpclass_nan_v4f64 +// CHECK-SAME: (ptr noalias nocapture writeonly sret(<4 x i64>) align 16 [[AGG_RESULT:%.*]], ptr nocapture noundef readonly [[TMP0:%.*]]) local_unnamed_addr #[[ATTR4:[0-9]+]] { +// CHECK-NEXT: entry: +// CHECK-NEXT: [[X:%.*]] = load <4 x double>, ptr [[TMP0]], align 16, !tbaa [[TBAA2:![0-9]+]] +// CHECK-NEXT: [[TMP1:%.*]] = fcmp uno <4 x double> [[X]], zeroinitializer +// CHECK-NEXT: [[TMP2:%.*]] = zext <4 x i1> [[TMP1]] to <4 x i64> +// CHECK-NEXT: store <4 x i64> [[TMP2]], ptr [[AGG_RESULT]], align 16, !tbaa [[TBAA2]] +// CHECK-NEXT: ret void +// +long4 check_isfpclass_nan_v4f64(double4 x) { + return __builtin_isfpclass(x, 3 /*NaN*/); +}