Skip to content

Commit 1ac3665

Browse files
authored
[clang] Restrict the use of scalar types in vector builtins (#119423)
This commit restricts the use of scalar types in vector math builtins, particularly the `__builtin_elementwise_*` builtins. Previously, small scalar integer types would be promoted to `int`, as per the usual conversions. This would silently do the wrong thing for certain operations, such as `add_sat`, `popcount`, `bitreverse`, and others. Similarly, since unsigned integer types were promoted to `int`, something like `add_sat(unsigned char, unsigned char)` would perform a *signed* operation. With this patch, promotable scalar integer types are not promoted to int, and are kept intact. If any of the types differ in the binary and ternary builtins, an error is issued. Similarly an error is issued if builtins are supplied integer types of different signs. Mixing enums of different types in binary/ternary builtins now consistently raises an error in all language modes. This brings the behaviour surrounding scalar types more in line with that of vector types. No change is made to vector types, which are both not promoted and whose element types must match. Fixes #84047. RFC: https://discourse.llvm.org/t/rfc-change-behaviour-of-elementwise-builtins-on-scalar-integer-types/83725
1 parent e902cf2 commit 1ac3665

File tree

9 files changed

+331
-109
lines changed

9 files changed

+331
-109
lines changed

clang/docs/LanguageExtensions.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,9 @@ The integer elementwise intrinsics, including ``__builtin_elementwise_popcount``
757757
``__builtin_elementwise_bitreverse``, ``__builtin_elementwise_add_sat``,
758758
``__builtin_elementwise_sub_sat`` can be called in a ``constexpr`` context.
759759

760+
No implicit promotion of integer types takes place. The mixing of integer types
761+
of different sizes and signs is forbidden in binary and ternary builtins.
762+
760763
============================================== ====================================================================== =========================================
761764
Name Operation Supported element types
762765
============================================== ====================================================================== =========================================

clang/include/clang/Sema/Sema.h

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2331,7 +2331,8 @@ class Sema final : public SemaBase {
23312331
const FunctionProtoType *Proto);
23322332

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

23372338
void checkLifetimeCaptureBy(FunctionDecl *FDecl, bool IsMemberFunction,
@@ -7499,10 +7500,15 @@ class Sema final : public SemaBase {
74997500
return K == ConditionKind::Switch ? Context.IntTy : Context.BoolTy;
75007501
}
75017502

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

7508+
// UsualUnaryFPConversions - promotes floating-point types according to the
7509+
// current language semantics.
7510+
ExprResult UsualUnaryFPConversions(Expr *E);
7511+
75067512
/// CallExprUnaryConversions - a special case of an unary conversion
75077513
/// performed on a function designator of a call expression.
75087514
ExprResult CallExprUnaryConversions(Expr *E);
@@ -7565,6 +7571,11 @@ class Sema final : public SemaBase {
75657571
ExprResult DefaultVariadicArgumentPromotion(Expr *E, VariadicCallType CT,
75667572
FunctionDecl *FDecl);
75677573

7574+
// Check that the usual arithmetic conversions can be performed on this pair
7575+
// of expressions that might be of enumeration type.
7576+
void checkEnumArithmeticConversions(Expr *LHS, Expr *RHS, SourceLocation Loc,
7577+
Sema::ArithConvKind ACK);
7578+
75687579
// UsualArithmeticConversions - performs the UsualUnaryConversions on it's
75697580
// operands and then handles various conversions that are common to binary
75707581
// operators (C99 6.3.1.8). If both operands aren't arithmetic, this

clang/lib/Sema/SemaChecking.cpp

Lines changed: 73 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14649,11 +14649,23 @@ void Sema::CheckAddressOfPackedMember(Expr *rhs) {
1464914649
_2, _3, _4));
1465014650
}
1465114651

14652+
// Performs a similar job to Sema::UsualUnaryConversions, but without any
14653+
// implicit promotion of integral/enumeration types.
14654+
static ExprResult BuiltinVectorMathConversions(Sema &S, Expr *E) {
14655+
// First, convert to an r-value.
14656+
ExprResult Res = S.DefaultFunctionArrayLvalueConversion(E);
14657+
if (Res.isInvalid())
14658+
return ExprError();
14659+
14660+
// Promote floating-point types.
14661+
return S.UsualUnaryFPConversions(Res.get());
14662+
}
14663+
1465214664
bool Sema::PrepareBuiltinElementwiseMathOneArgCall(CallExpr *TheCall) {
1465314665
if (checkArgCount(TheCall, 1))
1465414666
return true;
1465514667

14656-
ExprResult A = UsualUnaryConversions(TheCall->getArg(0));
14668+
ExprResult A = BuiltinVectorMathConversions(*this, TheCall->getArg(0));
1465714669
if (A.isInvalid())
1465814670
return true;
1465914671

@@ -14668,67 +14680,95 @@ bool Sema::PrepareBuiltinElementwiseMathOneArgCall(CallExpr *TheCall) {
1466814680
}
1466914681

1467014682
bool Sema::BuiltinElementwiseMath(CallExpr *TheCall, bool FPOnly) {
14671-
QualType Res;
14672-
if (BuiltinVectorMath(TheCall, Res, FPOnly))
14673-
return true;
14674-
TheCall->setType(Res);
14675-
return false;
14683+
if (auto Res = BuiltinVectorMath(TheCall, FPOnly); Res.has_value()) {
14684+
TheCall->setType(*Res);
14685+
return false;
14686+
}
14687+
return true;
1467614688
}
1467714689

1467814690
bool Sema::BuiltinVectorToScalarMath(CallExpr *TheCall) {
14679-
QualType Res;
14680-
if (BuiltinVectorMath(TheCall, Res))
14691+
std::optional<QualType> Res = BuiltinVectorMath(TheCall);
14692+
if (!Res)
1468114693
return true;
1468214694

14683-
if (auto *VecTy0 = Res->getAs<VectorType>())
14695+
if (auto *VecTy0 = (*Res)->getAs<VectorType>())
1468414696
TheCall->setType(VecTy0->getElementType());
1468514697
else
14686-
TheCall->setType(Res);
14698+
TheCall->setType(*Res);
1468714699

1468814700
return false;
1468914701
}
1469014702

14691-
bool Sema::BuiltinVectorMath(CallExpr *TheCall, QualType &Res, bool FPOnly) {
14703+
static bool checkBuiltinVectorMathMixedEnums(Sema &S, Expr *LHS, Expr *RHS,
14704+
SourceLocation Loc) {
14705+
QualType L = LHS->getEnumCoercedType(S.Context),
14706+
R = RHS->getEnumCoercedType(S.Context);
14707+
if (L->isUnscopedEnumerationType() && R->isUnscopedEnumerationType() &&
14708+
!S.Context.hasSameUnqualifiedType(L, R)) {
14709+
return S.Diag(Loc, diag::err_conv_mixed_enum_types_cxx26)
14710+
<< LHS->getSourceRange() << RHS->getSourceRange()
14711+
<< /*Arithmetic Between*/ 0 << L << R;
14712+
}
14713+
return false;
14714+
}
14715+
14716+
std::optional<QualType> Sema::BuiltinVectorMath(CallExpr *TheCall,
14717+
bool FPOnly) {
1469214718
if (checkArgCount(TheCall, 2))
14693-
return true;
14719+
return std::nullopt;
1469414720

14695-
ExprResult A = TheCall->getArg(0);
14696-
ExprResult B = TheCall->getArg(1);
14697-
// Do standard promotions between the two arguments, returning their common
14698-
// type.
14699-
Res = UsualArithmeticConversions(A, B, TheCall->getExprLoc(), ACK_Comparison);
14700-
if (A.isInvalid() || B.isInvalid())
14701-
return true;
14721+
if (checkBuiltinVectorMathMixedEnums(
14722+
*this, TheCall->getArg(0), TheCall->getArg(1), TheCall->getExprLoc()))
14723+
return std::nullopt;
1470214724

14703-
QualType TyA = A.get()->getType();
14704-
QualType TyB = B.get()->getType();
14725+
Expr *Args[2];
14726+
for (int I = 0; I < 2; ++I) {
14727+
ExprResult Converted =
14728+
BuiltinVectorMathConversions(*this, TheCall->getArg(I));
14729+
if (Converted.isInvalid())
14730+
return std::nullopt;
14731+
Args[I] = Converted.get();
14732+
}
1470514733

14706-
if (Res.isNull() || TyA.getCanonicalType() != TyB.getCanonicalType())
14707-
return Diag(A.get()->getBeginLoc(),
14708-
diag::err_typecheck_call_different_arg_types)
14709-
<< TyA << TyB;
14734+
SourceLocation LocA = Args[0]->getBeginLoc();
14735+
QualType TyA = Args[0]->getType();
14736+
QualType TyB = Args[1]->getType();
14737+
14738+
if (TyA.getCanonicalType() != TyB.getCanonicalType()) {
14739+
Diag(LocA, diag::err_typecheck_call_different_arg_types) << TyA << TyB;
14740+
return std::nullopt;
14741+
}
1471014742

1471114743
if (FPOnly) {
14712-
if (checkFPMathBuiltinElementType(*this, A.get()->getBeginLoc(), TyA, 1))
14713-
return true;
14744+
if (checkFPMathBuiltinElementType(*this, LocA, TyA, 1))
14745+
return std::nullopt;
1471414746
} else {
14715-
if (checkMathBuiltinElementType(*this, A.get()->getBeginLoc(), TyA, 1))
14716-
return true;
14747+
if (checkMathBuiltinElementType(*this, LocA, TyA, 1))
14748+
return std::nullopt;
1471714749
}
1471814750

14719-
TheCall->setArg(0, A.get());
14720-
TheCall->setArg(1, B.get());
14721-
return false;
14751+
TheCall->setArg(0, Args[0]);
14752+
TheCall->setArg(1, Args[1]);
14753+
return TyA;
1472214754
}
1472314755

1472414756
bool Sema::BuiltinElementwiseTernaryMath(CallExpr *TheCall,
1472514757
bool CheckForFloatArgs) {
1472614758
if (checkArgCount(TheCall, 3))
1472714759
return true;
1472814760

14761+
SourceLocation Loc = TheCall->getExprLoc();
14762+
if (checkBuiltinVectorMathMixedEnums(*this, TheCall->getArg(0),
14763+
TheCall->getArg(1), Loc) ||
14764+
checkBuiltinVectorMathMixedEnums(*this, TheCall->getArg(1),
14765+
TheCall->getArg(2), Loc))
14766+
return true;
14767+
1472914768
Expr *Args[3];
1473014769
for (int I = 0; I < 3; ++I) {
14731-
ExprResult Converted = UsualUnaryConversions(TheCall->getArg(I));
14770+
ExprResult Converted =
14771+
BuiltinVectorMathConversions(*this, TheCall->getArg(I));
1473214772
if (Converted.isInvalid())
1473314773
return true;
1473414774
Args[I] = Converted.get();

clang/lib/Sema/SemaExpr.cpp

Lines changed: 48 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -776,20 +776,11 @@ ExprResult Sema::CallExprUnaryConversions(Expr *E) {
776776
return Res.get();
777777
}
778778

779-
/// UsualUnaryConversions - Performs various conversions that are common to most
780-
/// operators (C99 6.3). The conversions of array and function types are
781-
/// sometimes suppressed. For example, the array->pointer conversion doesn't
782-
/// apply if the array is an argument to the sizeof or address (&) operators.
783-
/// In these instances, this routine should *not* be called.
784-
ExprResult Sema::UsualUnaryConversions(Expr *E) {
785-
// First, convert to an r-value.
786-
ExprResult Res = DefaultFunctionArrayLvalueConversion(E);
787-
if (Res.isInvalid())
788-
return ExprError();
789-
E = Res.get();
790-
779+
/// UsualUnaryFPConversions - Promotes floating-point types according to the
780+
/// current language semantics.
781+
ExprResult Sema::UsualUnaryFPConversions(Expr *E) {
791782
QualType Ty = E->getType();
792-
assert(!Ty.isNull() && "UsualUnaryConversions - missing type");
783+
assert(!Ty.isNull() && "UsualUnaryFPConversions - missing type");
793784

794785
LangOptions::FPEvalMethodKind EvalMethod = CurFPFeatures.getFPEvalMethod();
795786
if (EvalMethod != LangOptions::FEM_Source && Ty->isFloatingType() &&
@@ -827,7 +818,30 @@ ExprResult Sema::UsualUnaryConversions(Expr *E) {
827818

828819
// Half FP have to be promoted to float unless it is natively supported
829820
if (Ty->isHalfType() && !getLangOpts().NativeHalfType)
830-
return ImpCastExprToType(Res.get(), Context.FloatTy, CK_FloatingCast);
821+
return ImpCastExprToType(E, Context.FloatTy, CK_FloatingCast);
822+
823+
return E;
824+
}
825+
826+
/// UsualUnaryConversions - Performs various conversions that are common to most
827+
/// operators (C99 6.3). The conversions of array and function types are
828+
/// sometimes suppressed. For example, the array->pointer conversion doesn't
829+
/// apply if the array is an argument to the sizeof or address (&) operators.
830+
/// In these instances, this routine should *not* be called.
831+
ExprResult Sema::UsualUnaryConversions(Expr *E) {
832+
// First, convert to an r-value.
833+
ExprResult Res = DefaultFunctionArrayLvalueConversion(E);
834+
if (Res.isInvalid())
835+
return ExprError();
836+
837+
// Promote floating-point types.
838+
Res = UsualUnaryFPConversions(Res.get());
839+
if (Res.isInvalid())
840+
return ExprError();
841+
E = Res.get();
842+
843+
QualType Ty = E->getType();
844+
assert(!Ty.isNull() && "UsualUnaryConversions - missing type");
831845

832846
// Try to perform integral promotions if the object has a theoretically
833847
// promotable type.
@@ -1489,64 +1503,63 @@ static QualType handleFixedPointConversion(Sema &S, QualType LHSTy,
14891503

14901504
/// Check that the usual arithmetic conversions can be performed on this pair of
14911505
/// expressions that might be of enumeration type.
1492-
static void checkEnumArithmeticConversions(Sema &S, Expr *LHS, Expr *RHS,
1493-
SourceLocation Loc,
1494-
Sema::ArithConvKind ACK) {
1506+
void Sema::checkEnumArithmeticConversions(Expr *LHS, Expr *RHS,
1507+
SourceLocation Loc,
1508+
Sema::ArithConvKind ACK) {
14951509
// C++2a [expr.arith.conv]p1:
14961510
// If one operand is of enumeration type and the other operand is of a
14971511
// different enumeration type or a floating-point type, this behavior is
14981512
// deprecated ([depr.arith.conv.enum]).
14991513
//
15001514
// Warn on this in all language modes. Produce a deprecation warning in C++20.
15011515
// Eventually we will presumably reject these cases (in C++23 onwards?).
1502-
QualType L = LHS->getEnumCoercedType(S.Context),
1503-
R = RHS->getEnumCoercedType(S.Context);
1516+
QualType L = LHS->getEnumCoercedType(Context),
1517+
R = RHS->getEnumCoercedType(Context);
15041518
bool LEnum = L->isUnscopedEnumerationType(),
15051519
REnum = R->isUnscopedEnumerationType();
15061520
bool IsCompAssign = ACK == Sema::ACK_CompAssign;
15071521
if ((!IsCompAssign && LEnum && R->isFloatingType()) ||
15081522
(REnum && L->isFloatingType())) {
1509-
S.Diag(Loc, S.getLangOpts().CPlusPlus26
1510-
? diag::err_arith_conv_enum_float_cxx26
1511-
: S.getLangOpts().CPlusPlus20
1512-
? diag::warn_arith_conv_enum_float_cxx20
1513-
: diag::warn_arith_conv_enum_float)
1523+
Diag(Loc, getLangOpts().CPlusPlus26 ? diag::err_arith_conv_enum_float_cxx26
1524+
: getLangOpts().CPlusPlus20
1525+
? diag::warn_arith_conv_enum_float_cxx20
1526+
: diag::warn_arith_conv_enum_float)
15141527
<< LHS->getSourceRange() << RHS->getSourceRange() << (int)ACK << LEnum
15151528
<< L << R;
15161529
} else if (!IsCompAssign && LEnum && REnum &&
1517-
!S.Context.hasSameUnqualifiedType(L, R)) {
1530+
!Context.hasSameUnqualifiedType(L, R)) {
15181531
unsigned DiagID;
15191532
// In C++ 26, usual arithmetic conversions between 2 different enum types
15201533
// are ill-formed.
1521-
if (S.getLangOpts().CPlusPlus26)
1534+
if (getLangOpts().CPlusPlus26)
15221535
DiagID = diag::err_conv_mixed_enum_types_cxx26;
15231536
else if (!L->castAs<EnumType>()->getDecl()->hasNameForLinkage() ||
15241537
!R->castAs<EnumType>()->getDecl()->hasNameForLinkage()) {
15251538
// If either enumeration type is unnamed, it's less likely that the
15261539
// user cares about this, but this situation is still deprecated in
15271540
// C++2a. Use a different warning group.
1528-
DiagID = S.getLangOpts().CPlusPlus20
1529-
? diag::warn_arith_conv_mixed_anon_enum_types_cxx20
1530-
: diag::warn_arith_conv_mixed_anon_enum_types;
1541+
DiagID = getLangOpts().CPlusPlus20
1542+
? diag::warn_arith_conv_mixed_anon_enum_types_cxx20
1543+
: diag::warn_arith_conv_mixed_anon_enum_types;
15311544
} else if (ACK == Sema::ACK_Conditional) {
15321545
// Conditional expressions are separated out because they have
15331546
// historically had a different warning flag.
1534-
DiagID = S.getLangOpts().CPlusPlus20
1547+
DiagID = getLangOpts().CPlusPlus20
15351548
? diag::warn_conditional_mixed_enum_types_cxx20
15361549
: diag::warn_conditional_mixed_enum_types;
15371550
} else if (ACK == Sema::ACK_Comparison) {
15381551
// Comparison expressions are separated out because they have
15391552
// historically had a different warning flag.
1540-
DiagID = S.getLangOpts().CPlusPlus20
1553+
DiagID = getLangOpts().CPlusPlus20
15411554
? diag::warn_comparison_mixed_enum_types_cxx20
15421555
: diag::warn_comparison_mixed_enum_types;
15431556
} else {
1544-
DiagID = S.getLangOpts().CPlusPlus20
1557+
DiagID = getLangOpts().CPlusPlus20
15451558
? diag::warn_arith_conv_mixed_enum_types_cxx20
15461559
: diag::warn_arith_conv_mixed_enum_types;
15471560
}
1548-
S.Diag(Loc, DiagID) << LHS->getSourceRange() << RHS->getSourceRange()
1549-
<< (int)ACK << L << R;
1561+
Diag(Loc, DiagID) << LHS->getSourceRange() << RHS->getSourceRange()
1562+
<< (int)ACK << L << R;
15501563
}
15511564
}
15521565

@@ -1557,7 +1570,7 @@ static void checkEnumArithmeticConversions(Sema &S, Expr *LHS, Expr *RHS,
15571570
QualType Sema::UsualArithmeticConversions(ExprResult &LHS, ExprResult &RHS,
15581571
SourceLocation Loc,
15591572
ArithConvKind ACK) {
1560-
checkEnumArithmeticConversions(*this, LHS.get(), RHS.get(), Loc, ACK);
1573+
checkEnumArithmeticConversions(LHS.get(), RHS.get(), Loc, ACK);
15611574

15621575
if (ACK != ACK_CompAssign) {
15631576
LHS = UsualUnaryConversions(LHS.get());

0 commit comments

Comments
 (0)