[clang] [clang] Don't add built-in operator candidates that can't be viable (PR #227584)

via cfe-commits cfe-commits at lists.llvm.org
Wed Sep 30 00:09:51 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: Nico Weber (nico)

<details>
<summary>Changes</summary>

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% in instructions.

No intended behavior change.

---
Full diff: https://github.com/llvm/llvm-project/pull/227584.diff


2 Files Affected:

- (modified) clang/lib/Sema/SemaOverload.cpp (+36-9) 
- (added) clang/test/SemaCXX/overloaded-builtin-operators-arithmetic-pruning.cpp (+69) 


``````````diff
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 4adc9f8913eaf..65a52a3c7470f 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -9150,6 +9150,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;
@@ -9171,7 +9177,8 @@ class BuiltinCandidateTypeSet  {
 
   BuiltinCandidateTypeSet(Sema &SemaRef)
       : HasNonRecordTypes(false), HasArithmeticOrEnumeralTypes(false),
-        HasNullPtrType(false), SemaRef(SemaRef), Context(SemaRef.Context) {}
+        MayConvertToArithmetic(false), HasNullPtrType(false), SemaRef(SemaRef),
+        Context(SemaRef.Context) {}
 
   void AddTypesConvertedFrom(QualType Ty,
                              SourceLocation Loc,
@@ -9193,6 +9200,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; }
 };
 
@@ -9346,6 +9354,12 @@ BuiltinCandidateTypeSet::AddTypesConvertedFrom(QualType Ty,
   HasArithmeticOrEnumeralTypes =
     HasArithmeticOrEnumeralTypes || Ty->isArithmeticType();
 
+  // Flag if the type might convert to a promoted arithmetic or vector type.
+  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>()) {
@@ -9387,8 +9401,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()) {
@@ -9536,6 +9552,11 @@ class BuiltinOperatorOverloadBuilder {
   ArrayRef<Expr *> Args;
   QualifiersAndAtomic VisibleTypeConversionsQuals;
   bool HasArithmeticOrEnumeralCandidateType;
+  // Whether the candidates that only have arithmetic or vector parameter types
+  // can be viable: HasArithmeticOrEnumeralCandidateType is set, and every
+  // argument might convert to such a type. Nothing looks at non-viable builtin
+  // candidates, so these candidates are only added if this is set.
+  bool ArithmeticCandidatesMayBeViable;
   SmallVectorImpl<BuiltinCandidateTypeSet> &CandidateTypes;
   OverloadCandidateSet &CandidateSet;
 
@@ -9683,6 +9704,12 @@ class BuiltinOperatorOverloadBuilder {
         VisibleTypeConversionsQuals(VisibleTypeConversionsQuals),
         HasArithmeticOrEnumeralCandidateType(
             HasArithmeticOrEnumeralCandidateType),
+        ArithmeticCandidatesMayBeViable(
+            HasArithmeticOrEnumeralCandidateType &&
+            llvm::all_of(CandidateTypes,
+                         [](const BuiltinCandidateTypeSet &Types) {
+                           return Types.mayConvertToArithmetic();
+                         })),
         CandidateTypes(CandidateTypes), CandidateSet(CandidateSet) {
     InitArithmeticTypes();
   }
@@ -9707,7 +9734,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) {
@@ -9781,7 +9808,7 @@ class BuiltinOperatorOverloadBuilder {
   //       T         operator+(T);
   //       T         operator-(T);
   void addUnaryPlusOrMinusArithmeticOverloads() {
-    if (!HasArithmeticOrEnumeralCandidateType)
+    if (!ArithmeticCandidatesMayBeViable)
       return;
 
     for (unsigned Arith = FirstPromotedArithmeticType;
@@ -9811,7 +9838,7 @@ class BuiltinOperatorOverloadBuilder {
   //
   //        T         operator~(T);
   void addUnaryTildePromotedIntegralOverloads() {
-    if (!HasArithmeticOrEnumeralCandidateType)
+    if (!ArithmeticCandidatesMayBeViable)
       return;
 
     for (unsigned Int = FirstPromotedIntegralType;
@@ -10026,7 +10053,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;
@@ -10119,7 +10146,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;
@@ -10284,7 +10311,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) {
@@ -10338,7 +10365,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..ef8ea47d401d2
--- /dev/null
+++ b/clang/test/SemaCXX/overloaded-builtin-operators-arithmetic-pruning.cpp
@@ -0,0 +1,69 @@
+// 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 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,
+          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)(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}}
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/227584


More information about the cfe-commits mailing list