[clang] [clang] Don't add built-in operator candidates that can't be viable (PR #227584)
Nico Weber via cfe-commits
cfe-commits at lists.llvm.org
Wed Sep 30 00:08:43 PDT 2026
https://github.com/nico created https://github.com/llvm/llvm-project/pull/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% in instructions.
No intended behavior change.
>From 9a321f7aba3e869a622e87d40a915f57be9882e1 Mon Sep 17 00:00:00 2001
From: Nico Weber <thakis at chromium.org>
Date: Sun, 20 Sep 2026 11:14:13 -0400
Subject: [PATCH] [clang] Don't add built-in operator candidates that can't be
viable
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.
---
clang/lib/Sema/SemaOverload.cpp | 45 +++++++++---
...d-builtin-operators-arithmetic-pruning.cpp | 69 +++++++++++++++++++
2 files changed, 105 insertions(+), 9 deletions(-)
create mode 100644 clang/test/SemaCXX/overloaded-builtin-operators-arithmetic-pruning.cpp
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}}
+}
More information about the cfe-commits
mailing list