Author: Nico Weber Date: 2026-10-06T19:56:27Z New Revision: 6f6cf1ae76c45b8e080bd94d3cc9bd95a870fd1c
URL: https://github.com/llvm/llvm-project/commit/6f6cf1ae76c45b8e080bd94d3cc9bd95a870fd1c DIFF: https://github.com/llvm/llvm-project/commit/6f6cf1ae76c45b8e080bd94d3cc9bd95a870fd1c.diff LOG: [clang] Don't add built-in operator candidates that can't be viable (#227584) If an operand of an operator has (or converts to) an arithmetic or enumeration type, AddBuiltinOperatorCandidates() adds a built-in candidate for every pair of promoted arithmetic types, and each candidate tries to convert both operands. That's 121 candidates for `a == b` on arm64 macOS. If an operand can't convert to any of these types, none of these candidates are viable (e.g. `e == E::kFoo` for a scoped enumeration E). So conservatively track if each operand can convert to an arithmetic type, and only add these candidates if all operands can convert. Non-viable built-in candidates aren't used for anything and are safe to not add. For 60 random Chromium TUs (linux x64, -O2) picked with probability proportional to their compile time, sum over all TUs: CPU time: 186.6 s => 183.3 s, -1.81% instructions: 1901.2e9 => 1782.9e9, -6.23% mean max RSS: 587.7 MB => 587.0 MB (noise) (To check noise level, running this with the same binary twice: -0.03% CPU time, -0.01% instructions, +0.21% RSS.) Every TU improves, by 2.1% to 10.1% instructions. No intended behavior change. Added: clang/test/SemaCXX/overloaded-builtin-operators-arithmetic-pruning.cpp Modified: clang/lib/Sema/SemaOverload.cpp Removed: ################################################################################ diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index cd1bd2cecc490..e5049d11480ac 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -9197,6 +9197,12 @@ class BuiltinCandidateTypeSet { /// were present in the candidate set. bool HasArithmeticOrEnumeralTypes; + /// A flag indicating whether the candidate set has a type that might + /// convert to a promoted arithmetic type or to a vector type. This is + /// conservative: only scoped enumerations, pointers, member pointers, + /// nullptr_t, and classes that convert to nothing else are known not to. + bool MayConvertToArithmetic; + /// A flag indicating whether the nullptr type was present in the /// candidate set. bool HasNullPtrType; @@ -9222,8 +9228,8 @@ class BuiltinCandidateTypeSet { BuiltinCandidateTypeSet(Sema &SemaRef) : HasNonRecordTypes(false), HasArithmeticOrEnumeralTypes(false), - HasNullPtrType(false), HasReflectionType(false), SemaRef(SemaRef), - Context(SemaRef.Context) {} + MayConvertToArithmetic(false), HasNullPtrType(false), + HasReflectionType(false), SemaRef(SemaRef), Context(SemaRef.Context) {} void AddTypesConvertedFrom(QualType Ty, SourceLocation Loc, @@ -9245,6 +9251,7 @@ class BuiltinCandidateTypeSet { bool containsMatrixType(QualType Ty) const { return MatrixTypes.count(Ty); } bool hasNonRecordTypes() { return HasNonRecordTypes; } bool hasArithmeticOrEnumeralTypes() { return HasArithmeticOrEnumeralTypes; } + bool mayConvertToArithmetic() const { return MayConvertToArithmetic; } bool hasNullPtrType() const { return HasNullPtrType; } bool hasReflectionType() const { return HasReflectionType; } }; @@ -9399,6 +9406,13 @@ BuiltinCandidateTypeSet::AddTypesConvertedFrom(QualType Ty, HasArithmeticOrEnumeralTypes = HasArithmeticOrEnumeralTypes || Ty->isArithmeticType(); + // Flag if the type might convert to a promoted arithmetic or vector type. + // For records, this is set below when visiting their conversion functions. + MayConvertToArithmetic = + MayConvertToArithmetic || + !(TyIsRec || Ty->isScopedEnumeralType() || Ty->isAnyPointerType() || + Ty->isMemberPointerType() || Ty->isNullPtrType()); + if (Ty->isObjCIdType() || Ty->isObjCClassType()) PointerTypes.insert(Ty); else if (Ty->getAs<PointerType>() || Ty->getAs<ObjCObjectPointerType>()) { @@ -9442,8 +9456,10 @@ BuiltinCandidateTypeSet::AddTypesConvertedFrom(QualType Ty, // Skip conversion function templates; they don't tell us anything // about which builtin types we can convert to. - if (isa<FunctionTemplateDecl>(D)) + if (isa<FunctionTemplateDecl>(D)) { + MayConvertToArithmetic = true; continue; + } CXXConversionDecl *Conv = cast<CXXConversionDecl>(D); if (AllowExplicitConversions || !Conv->isExplicit()) { @@ -9590,7 +9606,10 @@ class BuiltinOperatorOverloadBuilder { Sema &S; ArrayRef<Expr *> Args; QualifiersAndAtomic VisibleTypeConversionsQuals; - bool HasArithmeticOrEnumeralCandidateType; + // Whether a candidate whose parameters are all arithmetic, vector or matrix + // types can be viable. It is viable if there is an arithmetic or enumeral + // candidate type, and every argument might convert to such a type. + bool ArithmeticCandidatesMayBeViable; SmallVectorImpl<BuiltinCandidateTypeSet> &CandidateTypes; OverloadCandidateSet &CandidateSet; @@ -9736,8 +9755,12 @@ class BuiltinOperatorOverloadBuilder { OverloadCandidateSet &CandidateSet) : S(S), Args(Args), VisibleTypeConversionsQuals(VisibleTypeConversionsQuals), - HasArithmeticOrEnumeralCandidateType( - HasArithmeticOrEnumeralCandidateType), + ArithmeticCandidatesMayBeViable( + HasArithmeticOrEnumeralCandidateType && + llvm::all_of(CandidateTypes, + [](const BuiltinCandidateTypeSet &Types) { + return Types.mayConvertToArithmetic(); + })), CandidateTypes(CandidateTypes), CandidateSet(CandidateSet) { InitArithmeticTypes(); } @@ -9762,7 +9785,7 @@ class BuiltinOperatorOverloadBuilder { // VQ T& operator--(VQ T&); // T operator--(VQ T&, int); void addPlusPlusMinusMinusArithmeticOverloads(OverloadedOperatorKind Op) { - if (!HasArithmeticOrEnumeralCandidateType) + if (!ArithmeticCandidatesMayBeViable) return; for (unsigned Arith = 0; Arith < NumArithmeticTypes; ++Arith) { @@ -9836,7 +9859,7 @@ class BuiltinOperatorOverloadBuilder { // T operator+(T); // T operator-(T); void addUnaryPlusOrMinusArithmeticOverloads() { - if (!HasArithmeticOrEnumeralCandidateType) + if (!ArithmeticCandidatesMayBeViable) return; for (unsigned Arith = FirstPromotedArithmeticType; @@ -9866,7 +9889,7 @@ class BuiltinOperatorOverloadBuilder { // // T operator~(T); void addUnaryTildePromotedIntegralOverloads() { - if (!HasArithmeticOrEnumeralCandidateType) + if (!ArithmeticCandidatesMayBeViable) return; for (unsigned Int = FirstPromotedIntegralType; @@ -10089,7 +10112,7 @@ class BuiltinOperatorOverloadBuilder { // between types L and R. // Our candidates ignore the first parameter. void addGenericBinaryArithmeticOverloads() { - if (!HasArithmeticOrEnumeralCandidateType) + if (!ArithmeticCandidatesMayBeViable) return; for (unsigned Left = FirstPromotedArithmeticType; @@ -10117,7 +10140,7 @@ class BuiltinOperatorOverloadBuilder { /// * (M2.getElementType(), M2) -> M2 /// * (M2, M2) -> M2 // Only if M2 is not part of CandidateTypes[0]. void addMatrixBinaryArithmeticOverloads() { - if (!HasArithmeticOrEnumeralCandidateType) + if (!ArithmeticCandidatesMayBeViable) return; for (QualType M1 : CandidateTypes[0].matrix_types()) { @@ -10182,7 +10205,7 @@ class BuiltinOperatorOverloadBuilder { // where LR is the result of the usual arithmetic conversions // between types L and R. void addBinaryBitwiseArithmeticOverloads() { - if (!HasArithmeticOrEnumeralCandidateType) + if (!ArithmeticCandidatesMayBeViable) return; for (unsigned Left = FirstPromotedIntegralType; @@ -10347,7 +10370,7 @@ class BuiltinOperatorOverloadBuilder { // VQ L& operator+=(VQ L&, R); // VQ L& operator-=(VQ L&, R); void addAssignmentArithmeticOverloads(bool isEqualOp) { - if (!HasArithmeticOrEnumeralCandidateType) + if (!ArithmeticCandidatesMayBeViable) return; for (unsigned Left = 0; Left < NumArithmeticTypes; ++Left) { @@ -10401,7 +10424,7 @@ class BuiltinOperatorOverloadBuilder { // VQ L& operator^=(VQ L&, R); // VQ L& operator|=(VQ L&, R); void addAssignmentIntegralOverloads() { - if (!HasArithmeticOrEnumeralCandidateType) + if (!ArithmeticCandidatesMayBeViable) return; for (unsigned Left = FirstIntegralType; Left < LastIntegralType; ++Left) { diff --git a/clang/test/SemaCXX/overloaded-builtin-operators-arithmetic-pruning.cpp b/clang/test/SemaCXX/overloaded-builtin-operators-arithmetic-pruning.cpp new file mode 100644 index 0000000000000..249e427bd4a5f --- /dev/null +++ b/clang/test/SemaCXX/overloaded-builtin-operators-arithmetic-pruning.cpp @@ -0,0 +1,71 @@ +// RUN: %clang_cc1 -fsyntax-only -std=c++20 -verify %s + +// The built-in operator candidates that only have arithmetic or vector +// parameter types are only added if every operand might convert to such a +// type. Check that this doesn't lose candidates. + +enum class Scoped { A }; +enum Unscoped { UA }; +typedef int Vec __attribute__((vector_size(16))); + +struct ConvTemplate { template <class T> operator T() const; }; +struct ConvInt { operator int() const; }; +struct ConvConvInt { operator ConvInt() const; }; +struct ConvIntRef { operator int &() const; }; +struct ConvScoped { operator Scoped() const; }; +struct ConvUnscoped { operator Unscoped() const; }; +struct ConvPtr { operator int *() const; }; +struct ConvVec { operator Vec() const; }; +struct ExplicitBool { explicit operator bool() const; }; + +Scoped operator|(Scoped, Scoped); + +void test(Scoped S, Unscoped U, ConvTemplate CT, ConvInt CI, ConvIntRef CIR, + ConvConvInt CCI, ConvScoped CS, ConvUnscoped CU, ConvPtr CP, ConvVec CV, + ExplicitBool EB, int I, int *P, Vec V, _Atomic(int) AI) { + // Conversion function templates might convert to anything. + (void)(CT == 1); // expected-error {{use of overloaded operator '==' is ambiguous (with operand types 'ConvTemplate' and 'int')}} \ + // expected-note-re 1+ {{built-in candidate operator==({{.*}}, int)}} + (void)(1 - CT); // expected-error {{use of overloaded operator '-' is ambiguous (with operand types 'int' and 'ConvTemplate')}} \ + // expected-note-re 1+ {{built-in candidate operator-(int, {{.*}})}} + + (void)(CI == 1); + (void)(CI + CI); + (void)(CI << CU); + (void)(-CI); + (void)(~CU); + (void)(U + CI); + (void)(U | U); + (void)(CI ? U : CI); + CIR += CI; + CIR |= CU; + ++CIR; + I += CI; + I <<= CU; + AI += CI; + AI |= CI; + (void)(AI == CI); + (void)(CP == P); + (void)(CP + CI); + (void)(CV + CV); + (void)(CV == V); + V += CV; + + (void)(S == Scoped::A); + (void)(S | S); + (void)(CS == Scoped::A); + + (void)(S == 1); // expected-error {{invalid operands to binary expression ('Scoped' and 'int')}} \ + // expected-note {{no implicit conversion for scoped enum}} + (void)(S + CI); // expected-error {{invalid operands to binary expression ('Scoped' and 'ConvInt')}} + (void)(CS == 1); // expected-error {{invalid operands to binary expression ('ConvScoped' and 'int')}} + (void)(CCI == 1); // expected-error {{invalid operands to binary expression ('ConvConvInt' and 'int')}} + (void)(CP == 1); // expected-error {{invalid operands to binary expression ('ConvPtr' and 'int')}} + (void)(EB == 1); // expected-error {{invalid operands to binary expression ('ExplicitBool' and 'int')}} + (void)(AI + S); // expected-error {{invalid operands to binary expression ('_Atomic(int)' and 'Scoped')}} \ + // expected-note {{no implicit conversion for scoped enum}} + I += S; // expected-error {{invalid operands to binary expression ('int' and 'Scoped')}} \ + // expected-note {{no implicit conversion for scoped enum}} + (void)(CV + S); // expected-error {{invalid operands to binary expression ('ConvVec' and 'Scoped')}} + (void)(-S); // expected-error {{invalid argument type 'Scoped' to unary expression}} +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
