Skip to content

[clang] Restrict the use of scalar types in vector builtins (#119423) #10646

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: swift/release/6.2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 29 additions & 36 deletions clang/docs/LanguageExtensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -647,42 +647,35 @@ elementwise to the input.

Unless specified otherwise operation(±0) = ±0 and operation(±infinity) = ±infinity

=========================================== ================================================================ =========================================
Name Operation Supported element types
=========================================== ================================================================ =========================================
T __builtin_elementwise_abs(T x) return the absolute value of a number x; the absolute value of signed integer and floating point types
the most negative integer remains the most negative integer
T __builtin_elementwise_fma(T x, T y, T z) fused multiply add, (x * y) + z. floating point types
T __builtin_elementwise_ceil(T x) return the smallest integral value greater than or equal to x floating point types
T __builtin_elementwise_sin(T x) return the sine of x interpreted as an angle in radians floating point types
T __builtin_elementwise_cos(T x) return the cosine of x interpreted as an angle in radians floating point types
T __builtin_elementwise_tan(T x) return the tangent of x interpreted as an angle in radians floating point types
T __builtin_elementwise_asin(T x) return the arcsine of x interpreted as an angle in radians floating point types
T __builtin_elementwise_acos(T x) return the arccosine of x interpreted as an angle in radians floating point types
T __builtin_elementwise_atan(T x) return the arctangent of x interpreted as an angle in radians floating point types
T __builtin_elementwise_sinh(T x) return the hyperbolic sine of angle x in radians floating point types
T __builtin_elementwise_cosh(T x) return the hyperbolic cosine of angle x in radians floating point types
T __builtin_elementwise_tanh(T x) return the hyperbolic tangent of angle x in radians floating point types
T __builtin_elementwise_floor(T x) return the largest integral value less than or equal to x floating point types
T __builtin_elementwise_log(T x) return the natural logarithm of x floating point types
T __builtin_elementwise_log2(T x) return the base 2 logarithm of x floating point types
T __builtin_elementwise_log10(T x) return the base 10 logarithm of x floating point types
T __builtin_elementwise_pow(T x, T y) return x raised to the power of y floating point types
T __builtin_elementwise_bitreverse(T x) return the integer represented after reversing the bits of x integer types
T __builtin_elementwise_exp(T x) returns the base-e exponential, e^x, of the specified value floating point types
T __builtin_elementwise_exp2(T x) returns the base-2 exponential, 2^x, of the specified value floating point types

T __builtin_elementwise_sqrt(T x) return the square root of a floating-point number floating point types
T __builtin_elementwise_roundeven(T x) round x to the nearest integer value in floating point format, floating point types
rounding halfway cases to even (that is, to the nearest value
that is an even integer), regardless of the current rounding
direction.
T __builtin_elementwise_round(T x) round x to the nearest integer value in floating point format, floating point types
rounding halfway cases away from zero, regardless of the
current rounding direction. May raise floating-point
exceptions.
T __builtin_elementwise_trunc(T x) return the integral value nearest to but no larger in floating point types
magnitude than x
No implicit promotion of integer types takes place. The mixing of integer types
of different sizes and signs is forbidden in binary and ternary builtins.

============================================== ====================================================================== =========================================
Name Operation Supported element types
============================================== ====================================================================== =========================================
T __builtin_elementwise_abs(T x) return the absolute value of a number x; the absolute value of signed integer and floating point types
the most negative integer remains the most negative integer
T __builtin_elementwise_fma(T x, T y, T z) fused multiply add, (x * y) + z. floating point types
T __builtin_elementwise_ceil(T x) return the smallest integral value greater than or equal to x floating point types
T __builtin_elementwise_sin(T x) return the sine of x interpreted as an angle in radians floating point types
T __builtin_elementwise_cos(T x) return the cosine of x interpreted as an angle in radians floating point types
T __builtin_elementwise_tan(T x) return the tangent of x interpreted as an angle in radians floating point types
T __builtin_elementwise_asin(T x) return the arcsine of x interpreted as an angle in radians floating point types
T __builtin_elementwise_acos(T x) return the arccosine of x interpreted as an angle in radians floating point types
T __builtin_elementwise_atan(T x) return the arctangent of x interpreted as an angle in radians floating point types
T __builtin_elementwise_atan2(T y, T x) return the arctangent of y/x floating point types
T __builtin_elementwise_sinh(T x) return the hyperbolic sine of angle x in radians floating point types
T __builtin_elementwise_cosh(T x) return the hyperbolic cosine of angle x in radians floating point types
T __builtin_elementwise_tanh(T x) return the hyperbolic tangent of angle x in radians floating point types
T __builtin_elementwise_floor(T x) return the largest integral value less than or equal to x floating point types
T __builtin_elementwise_log(T x) return the natural logarithm of x floating point types
T __builtin_elementwise_log2(T x) return the base 2 logarithm of x floating point types
T __builtin_elementwise_log10(T x) return the base 10 logarithm of x floating point types
T __builtin_elementwise_popcount(T x) return the number of 1 bits in x integer types
T __builtin_elementwise_pow(T x, T y) return x raised to the power of y floating point types
T __builtin_elementwise_bitreverse(T x) return the integer represented after reversing the bits of x integer types
T __builtin_elementwise_exp(T x) returns the base-e exponential, e^x, of the specified value floating point types
T __builtin_elementwise_exp2(T x) returns the base-2 exponential, 2^x, of the specified value floating point types

T __builtin_elementwise_nearbyint(T x) round x to the nearest integer value in floating point format, floating point types
rounding according to the current rounding direction.
Expand Down
23 changes: 18 additions & 5 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -2551,7 +2551,9 @@ class Sema final : public SemaBase {
bool CheckFunctionCall(FunctionDecl *FDecl, CallExpr *TheCall,
const FunctionProtoType *Proto);

bool BuiltinVectorMath(CallExpr *TheCall, QualType &Res);
/// \param FPOnly restricts the arguments to floating-point types.
std::optional<QualType> BuiltinVectorMath(CallExpr *TheCall,
bool FPOnly = false);
bool BuiltinVectorToScalarMath(CallExpr *TheCall);

/// Handles the checks for format strings, non-POD arguments to vararg
Expand Down Expand Up @@ -2762,8 +2764,9 @@ class Sema final : public SemaBase {
ExprResult AtomicOpsOverloaded(ExprResult TheCallResult,
AtomicExpr::AtomicOp Op);

bool BuiltinElementwiseMath(CallExpr *TheCall);
bool PrepareBuiltinReduceMathOneArgCall(CallExpr *TheCall);
bool BuiltinElementwiseMath(CallExpr *TheCall, bool FPOnly);
bool PrepareBuiltinReduceMathOneArgCall(CallExpr *TheCall,
bool FPOnly = false);

bool BuiltinNonDeterministicValue(CallExpr *TheCall);

Expand Down Expand Up @@ -7751,10 +7754,15 @@ class Sema final : public SemaBase {
return K == ConditionKind::Switch ? Context.IntTy : Context.BoolTy;
}

// UsualUnaryConversions - promotes integers (C99 6.3.1.1p2) and converts
// functions and arrays to their respective pointers (C99 6.3.2.1).
// UsualUnaryConversions - promotes integers (C99 6.3.1.1p2), converts
// functions and arrays to their respective pointers (C99 6.3.2.1), and
// promotes floating-piont types according to the language semantics.
ExprResult UsualUnaryConversions(Expr *E);

// UsualUnaryFPConversions - promotes floating-point types according to the
// current language semantics.
ExprResult UsualUnaryFPConversions(Expr *E);

/// CallExprUnaryConversions - a special case of an unary conversion
/// performed on a function designator of a call expression.
ExprResult CallExprUnaryConversions(Expr *E);
Expand Down Expand Up @@ -7829,6 +7837,11 @@ class Sema final : public SemaBase {
ExprResult DefaultVariadicArgumentPromotion(Expr *E, VariadicCallType CT,
FunctionDecl *FDecl);

// Check that the usual arithmetic conversions can be performed on this pair
// of expressions that might be of enumeration type.
void checkEnumArithmeticConversions(Expr *LHS, Expr *RHS, SourceLocation Loc,
Sema::ArithConvKind ACK);

// UsualArithmeticConversions - performs the UsualUnaryConversions on it's
// operands and then handles various conversions that are common to binary
// operators (C99 6.3.1.8). If both operands aren't arithmetic, this
Expand Down
117 changes: 81 additions & 36 deletions clang/lib/Sema/SemaChecking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2711,7 +2711,7 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
// These builtins restrict the element type to floating point
// types only, and take in two arguments.
case Builtin::BI__builtin_elementwise_pow: {
if (BuiltinElementwiseMath(TheCall))
if (BuiltinElementwiseMath(TheCall, true))
return ExprError();

QualType ArgTy = TheCall->getArg(0)->getType();
Expand All @@ -2727,7 +2727,7 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
// types only.
case Builtin::BI__builtin_elementwise_add_sat:
case Builtin::BI__builtin_elementwise_sub_sat: {
if (BuiltinElementwiseMath(TheCall))
if (BuiltinElementwiseMath(TheCall, false))
return ExprError();

const Expr *Arg = TheCall->getArg(0);
Expand All @@ -2747,7 +2747,7 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,

case Builtin::BI__builtin_elementwise_min:
case Builtin::BI__builtin_elementwise_max:
if (BuiltinElementwiseMath(TheCall))
if (BuiltinElementwiseMath(TheCall, false))
return ExprError();
break;

Expand Down Expand Up @@ -14899,11 +14899,23 @@ void Sema::CheckAddressOfPackedMember(Expr *rhs) {
_2, _3, _4));
}

// Performs a similar job to Sema::UsualUnaryConversions, but without any
// implicit promotion of integral/enumeration types.
static ExprResult BuiltinVectorMathConversions(Sema &S, Expr *E) {
// First, convert to an r-value.
ExprResult Res = S.DefaultFunctionArrayLvalueConversion(E);
if (Res.isInvalid())
return ExprError();

// Promote floating-point types.
return S.UsualUnaryFPConversions(Res.get());
}

bool Sema::PrepareBuiltinElementwiseMathOneArgCall(CallExpr *TheCall) {
if (checkArgCount(TheCall, 1))
return true;

ExprResult A = UsualUnaryConversions(TheCall->getArg(0));
ExprResult A = BuiltinVectorMathConversions(*this, TheCall->getArg(0));
if (A.isInvalid())
return true;

Expand All @@ -14917,63 +14929,96 @@ bool Sema::PrepareBuiltinElementwiseMathOneArgCall(CallExpr *TheCall) {
return false;
}

bool Sema::BuiltinElementwiseMath(CallExpr *TheCall) {
QualType Res;
if (BuiltinVectorMath(TheCall, Res))
return true;
TheCall->setType(Res);
return false;
bool Sema::BuiltinElementwiseMath(CallExpr *TheCall, bool FPOnly) {
if (auto Res = BuiltinVectorMath(TheCall, FPOnly); Res.has_value()) {
TheCall->setType(*Res);
return false;
}
return true;
}

bool Sema::BuiltinVectorToScalarMath(CallExpr *TheCall) {
QualType Res;
if (BuiltinVectorMath(TheCall, Res))
std::optional<QualType> Res = BuiltinVectorMath(TheCall);
if (!Res)
return true;

if (auto *VecTy0 = Res->getAs<VectorType>())
if (auto *VecTy0 = (*Res)->getAs<VectorType>())
TheCall->setType(VecTy0->getElementType());
else
TheCall->setType(Res);
TheCall->setType(*Res);

return false;
}

bool Sema::BuiltinVectorMath(CallExpr *TheCall, QualType &Res) {
static bool checkBuiltinVectorMathMixedEnums(Sema &S, Expr *LHS, Expr *RHS,
SourceLocation Loc) {
QualType L = LHS->getEnumCoercedType(S.Context),
R = RHS->getEnumCoercedType(S.Context);
if (L->isUnscopedEnumerationType() && R->isUnscopedEnumerationType() &&
!S.Context.hasSameUnqualifiedType(L, R)) {
return S.Diag(Loc, diag::err_conv_mixed_enum_types_cxx26)
<< LHS->getSourceRange() << RHS->getSourceRange()
<< /*Arithmetic Between*/ 0 << L << R;
}
return false;
}

std::optional<QualType> Sema::BuiltinVectorMath(CallExpr *TheCall,
bool FPOnly) {
if (checkArgCount(TheCall, 2))
return true;
return std::nullopt;

ExprResult A = TheCall->getArg(0);
ExprResult B = TheCall->getArg(1);
// Do standard promotions between the two arguments, returning their common
// type.
Res = UsualArithmeticConversions(A, B, TheCall->getExprLoc(), ACK_Comparison);
if (A.isInvalid() || B.isInvalid())
return true;
if (checkBuiltinVectorMathMixedEnums(
*this, TheCall->getArg(0), TheCall->getArg(1), TheCall->getExprLoc()))
return std::nullopt;

QualType TyA = A.get()->getType();
QualType TyB = B.get()->getType();
Expr *Args[2];
for (int I = 0; I < 2; ++I) {
ExprResult Converted =
BuiltinVectorMathConversions(*this, TheCall->getArg(I));
if (Converted.isInvalid())
return std::nullopt;
Args[I] = Converted.get();
}

if (Res.isNull() || TyA.getCanonicalType() != TyB.getCanonicalType())
return Diag(A.get()->getBeginLoc(),
diag::err_typecheck_call_different_arg_types)
<< TyA << TyB;
SourceLocation LocA = Args[0]->getBeginLoc();
QualType TyA = Args[0]->getType();
QualType TyB = Args[1]->getType();

if (checkMathBuiltinElementType(*this, A.get()->getBeginLoc(), TyA, 1))
return true;
if (TyA.getCanonicalType() != TyB.getCanonicalType()) {
Diag(LocA, diag::err_typecheck_call_different_arg_types) << TyA << TyB;
return std::nullopt;
}

TheCall->setArg(0, A.get());
TheCall->setArg(1, B.get());
return false;
if (FPOnly) {
if (checkFPMathBuiltinElementType(*this, LocA, TyA, 1))
return std::nullopt;
} else {
if (checkMathBuiltinElementType(*this, LocA, TyA, 1))
return std::nullopt;
}

TheCall->setArg(0, Args[0]);
TheCall->setArg(1, Args[1]);
return TyA;
}

bool Sema::BuiltinElementwiseTernaryMath(CallExpr *TheCall,
bool CheckForFloatArgs) {
if (checkArgCount(TheCall, 3))
return true;

SourceLocation Loc = TheCall->getExprLoc();
if (checkBuiltinVectorMathMixedEnums(*this, TheCall->getArg(0),
TheCall->getArg(1), Loc) ||
checkBuiltinVectorMathMixedEnums(*this, TheCall->getArg(1),
TheCall->getArg(2), Loc))
return true;

Expr *Args[3];
for (int I = 0; I < 3; ++I) {
ExprResult Converted = UsualUnaryConversions(TheCall->getArg(I));
ExprResult Converted =
BuiltinVectorMathConversions(*this, TheCall->getArg(I));
if (Converted.isInvalid())
return true;
Args[I] = Converted.get();
Expand Down Expand Up @@ -15010,7 +15055,7 @@ bool Sema::BuiltinElementwiseTernaryMath(CallExpr *TheCall,
return false;
}

bool Sema::PrepareBuiltinReduceMathOneArgCall(CallExpr *TheCall) {
bool Sema::PrepareBuiltinReduceMathOneArgCall(CallExpr *TheCall, bool FPOnly) {
if (checkArgCount(TheCall, 1))
return true;

Expand Down
Loading