Skip to content

Commit

Permalink
[clang] Support vectors in __builtin_isfpclass
Browse files Browse the repository at this point in the history
Builtin function `__builtin_isfpclass` now can be called for a vector
of floating-point values. In this case it is applied to the vector
elementwise and produces vector of integer values.

Differential Revision: https://reviews.llvm.org/D153339
  • Loading branch information
spavloff committed Sep 2, 2023
1 parent 678e3ee commit 9fd57e4
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 21 deletions.
22 changes: 14 additions & 8 deletions clang/docs/LanguageExtensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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**:
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
------------
Expand Down
30 changes: 25 additions & 5 deletions clang/lib/Sema/SemaChecking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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<VectorType>()->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;
}

Expand Down
57 changes: 49 additions & 8 deletions clang/test/CodeGen/isfpclass.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -122,11 +122,52 @@ _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) {
#pragma STDC FENV_ACCESS ON
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*/);
}

0 comments on commit 9fd57e4

Please sign in to comment.