Skip to content

[Clang] Bypass TAD during overload resolution if a perfect match exists #136203

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

Merged
merged 32 commits into from
Apr 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
8a67156
[Clang][WIP][RFC] Bypass TAD during overload resolution if a perfect …
cor3ntin Mar 27, 2025
f1f96a6
avoid comparing types
cor3ntin Mar 29, 2025
1144ae4
Fix logic
cor3ntin Mar 29, 2025
b35a6c4
optimize
cor3ntin Mar 29, 2025
401772d
optimize again
cor3ntin Mar 29, 2025
a5acdde
Cleanups, fix tests
cor3ntin Mar 31, 2025
a415862
Fix templight test
cor3ntin Mar 31, 2025
e75040e
format
cor3ntin Mar 31, 2025
3257b7f
do not skip templates when initializing by constructors as that might…
cor3ntin Mar 31, 2025
2a1b90b
move and document the special handling of implicit object member func…
cor3ntin Mar 31, 2025
9165cd4
Fix typos and cuda tests
cor3ntin Mar 31, 2025
89fc496
remove redundant check
cor3ntin Mar 31, 2025
e3b231b
More code simplification
cor3ntin Apr 1, 2025
5c60ecf
fix cuda, add comments
cor3ntin Apr 1, 2025
1253aae
add tests
cor3ntin Apr 1, 2025
33336e0
* Fix handling of explicit specifiers
cor3ntin Apr 1, 2025
a34b533
slab allocate template candidates
cor3ntin Apr 3, 2025
76a860f
fix rvalue to lvalue binding
cor3ntin Apr 3, 2025
45edd3e
Disable resolution by perfect match when
cor3ntin Apr 3, 2025
d580d93
address Erich's, format, add release notes
cor3ntin Apr 15, 2025
533e8df
address more of Erich's feedback
cor3ntin Apr 15, 2025
037e54c
Clarify changelog
cor3ntin Apr 15, 2025
4cbb11c
fix assertion
cor3ntin Apr 15, 2025
9e95b94
cleanups
cor3ntin Apr 16, 2025
a8e7b48
fix assertion
cor3ntin Apr 16, 2025
9f15bd3
Cleaner approach
cor3ntin Apr 16, 2025
6611bad
Add comment
cor3ntin Apr 16, 2025
64716b4
Add asserts
cor3ntin Apr 16, 2025
595ca87
fix missplaced assert
cor3ntin Apr 16, 2025
8764a6a
typo
cor3ntin Apr 17, 2025
fb78e0e
Fix assert
cor3ntin Apr 17, 2025
107445e
formatting
cor3ntin Apr 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ C++ Language Changes
asm((std::string_view("nop")) ::: (std::string_view("memory")));
}

- Clang now implements the changes to overload resolution proposed by section 1 and 2 of
`P3606 <https://wg21.link/P3606R0>`_. If a non-template candidate exists in an overload set that is
a perfect match (all conversion sequences are identity conversions) template candidates are not instantiated.
Diagnostics that would have resulted from the instantiation of these template candidates are no longer
produced. This aligns Clang closer to the behavior of GCC, and fixes (#GH62096), (#GH74581), and (#GH74581).

C++2c Feature Support
^^^^^^^^^^^^^^^^^^^^^

Expand Down
240 changes: 230 additions & 10 deletions clang/include/clang/Sema/Overload.h
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,34 @@ class Sema;
Third == ICK_Identity;
}

/// A conversion sequence is perfect if it is an identity conversion and
/// the type of the source is the same as the type of the target.
bool isPerfect(const ASTContext &C) const {
if (!isIdentityConversion())
return false;
// If we are not performing a reference binding, we can skip comparing
// the types, which has a noticeable performance impact.
if (!ReferenceBinding) {
#ifndef NDEBUG
auto Decay = [&](QualType T) {
return (T->isArrayType() || T->isFunctionType()) ? C.getDecayedType(T)
: T;
};
// The types might differ if there is an array-to-pointer conversion
// an function-to-pointer conversion, or lvalue-to-rvalue conversion.
// In some cases, this may happen even if First is not set.
assert(C.hasSameUnqualifiedType(Decay(getFromType()),
Decay(getToType(2))));
#endif
return true;
}
if (!C.hasSameType(getFromType(), getToType(2)))
return false;
if (BindsToRvalue && IsLvalueReference)
return false;
return true;
}

ImplicitConversionRank getRank() const;
NarrowingKind
getNarrowingKind(ASTContext &Context, const Expr *Converted,
Expand Down Expand Up @@ -743,6 +771,12 @@ class Sema;
Standard.setAllToTypes(T);
}

/// A conversion sequence is perfect if it is an identity conversion and
/// the type of the source is the same as the type of the target.
bool isPerfect(const ASTContext &C) const {
return isStandard() && Standard.isPerfect(C);
}

// True iff this is a conversion sequence from an initializer list to an
// array or std::initializer.
bool hasInitializerListContainerType() const {
Expand Down Expand Up @@ -939,6 +973,10 @@ class Sema;
LLVM_PREFERRED_TYPE(CallExpr::ADLCallKind)
unsigned IsADLCandidate : 1;

/// Whether FinalConversion has been set.
LLVM_PREFERRED_TYPE(bool)
unsigned HasFinalConversion : 1;

/// Whether this is a rewritten candidate, and if so, of what kind?
LLVM_PREFERRED_TYPE(OverloadCandidateRewriteKind)
unsigned RewriteKind : 2;
Expand Down Expand Up @@ -979,6 +1017,20 @@ class Sema;
return false;
}

// An overload is a perfect match if the conversion
// sequences for each argument are perfect.
bool isPerfectMatch(const ASTContext &Ctx) const {
if (!Viable)
return false;
for (const auto &C : Conversions) {
if (!C.isInitialized() || !C.isPerfect(Ctx))
return false;
}
if (HasFinalConversion)
return FinalConversion.isPerfect(Ctx);
return true;
}

bool TryToFixBadConversion(unsigned Idx, Sema &S) {
bool CanFix = Fix.tryToFixConversion(
Conversions[Idx].Bad.FromExpr,
Expand Down Expand Up @@ -1012,8 +1064,67 @@ class Sema;
: IsSurrogate(false), IgnoreObjectArgument(false),
TookAddressOfOverload(false), StrictPackMatch(false),
IsADLCandidate(llvm::to_underlying(CallExpr::NotADL)),
RewriteKind(CRK_None) {}
HasFinalConversion(false), RewriteKind(CRK_None) {}
};

struct DeferredTemplateOverloadCandidate {

// intrusive linked list support for allocateDeferredCandidate
DeferredTemplateOverloadCandidate *Next = nullptr;

enum Kind { Function, Method, Conversion };

LLVM_PREFERRED_TYPE(Kind)
unsigned Kind : 2;
LLVM_PREFERRED_TYPE(bool)
unsigned AllowObjCConversionOnExplicit : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned AllowResultConversion : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned AllowExplicit : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned SuppressUserConversions : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned PartialOverloading : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned AggregateCandidateDeduction : 1;
};

struct DeferredFunctionTemplateOverloadCandidate
: public DeferredTemplateOverloadCandidate {
FunctionTemplateDecl *FunctionTemplate;
DeclAccessPair FoundDecl;
ArrayRef<Expr *> Args;
CallExpr::ADLCallKind IsADLCandidate;
OverloadCandidateParamOrder PO;
};
static_assert(std::is_trivially_destructible_v<
DeferredFunctionTemplateOverloadCandidate>);

struct DeferredMethodTemplateOverloadCandidate
: public DeferredTemplateOverloadCandidate {
FunctionTemplateDecl *FunctionTemplate;
DeclAccessPair FoundDecl;
ArrayRef<Expr *> Args;
CXXRecordDecl *ActingContext;
Expr::Classification ObjectClassification;
QualType ObjectType;
OverloadCandidateParamOrder PO;
};
static_assert(std::is_trivially_destructible_v<
DeferredMethodTemplateOverloadCandidate>);

struct DeferredConversionTemplateOverloadCandidate
: public DeferredTemplateOverloadCandidate {
FunctionTemplateDecl *FunctionTemplate;
DeclAccessPair FoundDecl;
CXXRecordDecl *ActingContext;
Expr *From;
QualType ToType;
};

static_assert(std::is_trivially_destructible_v<
DeferredConversionTemplateOverloadCandidate>);

/// OverloadCandidateSet - A set of overload candidates, used in C++
/// overload resolution (C++ 13.3).
Expand Down Expand Up @@ -1043,6 +1154,11 @@ class Sema;
/// C++ [over.match.call.general]
/// Resolve a call through the address of an overload set.
CSK_AddressOfOverloadSet,

/// When doing overload resolution during code completion,
/// we want to show all viable candidates, including otherwise
/// deferred template candidates.
CSK_CodeCompletion,
};

/// Information about operator rewrites to consider when adding operator
Expand Down Expand Up @@ -1117,16 +1233,27 @@ class Sema;
SmallVector<OverloadCandidate, 16> Candidates;
llvm::SmallPtrSet<uintptr_t, 16> Functions;

// Allocator for ConversionSequenceLists. We store the first few of these
DeferredTemplateOverloadCandidate *FirstDeferredCandidate = nullptr;
unsigned DeferredCandidatesCount : 8 * sizeof(unsigned) - 2;
LLVM_PREFERRED_TYPE(bool)
unsigned HasDeferredTemplateConstructors : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned ResolutionByPerfectCandidateIsDisabled : 1;

// Allocator for ConversionSequenceLists and deferred candidate args.
// We store the first few of these
// inline to avoid allocation for small sets.
llvm::BumpPtrAllocator SlabAllocator;

SourceLocation Loc;
CandidateSetKind Kind;
OperatorRewriteInfo RewriteInfo;

/// Small storage size for ImplicitConversionSequences
/// and the persisted arguments of deferred candidates.
constexpr static unsigned NumInlineBytes =
24 * sizeof(ImplicitConversionSequence);
32 * sizeof(ImplicitConversionSequence);

unsigned NumInlineBytesUsed = 0;
alignas(void *) char InlineSpace[NumInlineBytes];

Expand All @@ -1137,15 +1264,13 @@ class Sema;
/// from the slab allocator.
/// FIXME: It would probably be nice to have a SmallBumpPtrAllocator
/// instead.
/// FIXME: Now that this only allocates ImplicitConversionSequences, do we
/// want to un-generalize this?
template <typename T>
T *slabAllocate(unsigned N) {
// It's simpler if this doesn't need to consider alignment.
static_assert(alignof(T) == alignof(void *),
"Only works for pointer-aligned types.");
static_assert(std::is_trivial<T>::value ||
std::is_same<ImplicitConversionSequence, T>::value,
static_assert(std::is_trivially_destructible_v<T> ||
(std::is_same_v<ImplicitConversionSequence, T>),
"Add destruction logic to OverloadCandidateSet::clear().");

unsigned NBytes = sizeof(T) * N;
Expand All @@ -1159,12 +1284,34 @@ class Sema;
return reinterpret_cast<T *>(FreeSpaceStart);
}

// Because the size of OverloadCandidateSet has a noticeable impact on
// performance, we store each deferred template candidate in the slab
// allocator such that deferred candidates are ultimately a singly-linked
// intrusive linked list. This ends up being much more efficient than a
// SmallVector that is empty in the common case.
template <typename T> T *allocateDeferredCandidate() {
T *C = slabAllocate<T>(1);
if (!FirstDeferredCandidate)
FirstDeferredCandidate = C;
else {
auto *F = FirstDeferredCandidate;
while (F->Next)
F = F->Next;
F->Next = C;
}
DeferredCandidatesCount++;
return C;
}

void destroyCandidates();

public:
OverloadCandidateSet(SourceLocation Loc, CandidateSetKind CSK,
OperatorRewriteInfo RewriteInfo = {})
: Loc(Loc), Kind(CSK), RewriteInfo(RewriteInfo) {}
: FirstDeferredCandidate(nullptr), DeferredCandidatesCount(0),
HasDeferredTemplateConstructors(false),
ResolutionByPerfectCandidateIsDisabled(false), Loc(Loc), Kind(CSK),
RewriteInfo(RewriteInfo) {}
OverloadCandidateSet(const OverloadCandidateSet &) = delete;
OverloadCandidateSet &operator=(const OverloadCandidateSet &) = delete;
~OverloadCandidateSet() { destroyCandidates(); }
Expand All @@ -1176,6 +1323,9 @@ class Sema;
/// Whether diagnostics should be deferred.
bool shouldDeferDiags(Sema &S, ArrayRef<Expr *> Args, SourceLocation OpLoc);

// Whether the resolution of template candidates should be deferred
bool shouldDeferTemplateArgumentDeduction(const LangOptions &Opts) const;

/// Determine when this overload candidate will be new to the
/// overload set.
bool isNewCandidate(Decl *F, OverloadCandidateParamOrder PO =
Expand All @@ -1199,8 +1349,10 @@ class Sema;
iterator begin() { return Candidates.begin(); }
iterator end() { return Candidates.end(); }

size_t size() const { return Candidates.size(); }
bool empty() const { return Candidates.empty(); }
size_t size() const { return Candidates.size() + DeferredCandidatesCount; }
bool empty() const {
return Candidates.empty() && DeferredCandidatesCount == 0;
}

/// Allocate storage for conversion sequences for NumConversions
/// conversions.
Expand All @@ -1216,6 +1368,24 @@ class Sema;
return ConversionSequenceList(Conversions, NumConversions);
}

/// Provide storage for any Expr* arg that must be preserved
/// until deferred template candidates are deduced.
/// Typically this should be used for reversed operator arguments
/// and any time the argument array is transformed while adding
/// a template candidate.
llvm::MutableArrayRef<Expr *> getPersistentArgsArray(unsigned N) {
Expr **Exprs = slabAllocate<Expr *>(N);
return llvm::MutableArrayRef<Expr *>(Exprs, N);
}

template <typename... T>
llvm::MutableArrayRef<Expr *> getPersistentArgsArray(T *...Exprs) {
llvm::MutableArrayRef<Expr *> Arr =
getPersistentArgsArray(sizeof...(Exprs));
llvm::copy(std::initializer_list<Expr *>{Exprs...}, Arr.data());
return Arr;
}

/// Add a new candidate with NumConversions conversion sequence slots
/// to the overload set.
OverloadCandidate &addCandidate(unsigned NumConversions = 0,
Expand All @@ -1231,6 +1401,32 @@ class Sema;
return C;
}

void AddDeferredTemplateCandidate(
FunctionTemplateDecl *FunctionTemplate, DeclAccessPair FoundDecl,
ArrayRef<Expr *> Args, bool SuppressUserConversions,
bool PartialOverloading, bool AllowExplicit,
CallExpr::ADLCallKind IsADLCandidate, OverloadCandidateParamOrder PO,
bool AggregateCandidateDeduction);

void AddDeferredMethodTemplateCandidate(
FunctionTemplateDecl *MethodTmpl, DeclAccessPair FoundDecl,
CXXRecordDecl *ActingContext, QualType ObjectType,
Expr::Classification ObjectClassification, ArrayRef<Expr *> Args,
bool SuppressUserConversions, bool PartialOverloading,
OverloadCandidateParamOrder PO);

void AddDeferredConversionTemplateCandidate(
FunctionTemplateDecl *FunctionTemplate, DeclAccessPair FoundDecl,
CXXRecordDecl *ActingContext, Expr *From, QualType ToType,
bool AllowObjCConversionOnExplicit, bool AllowExplicit,
bool AllowResultConversion);

void InjectNonDeducedTemplateCandidates(Sema &S);

void DisableResolutionByPerfectCandidate() {
ResolutionByPerfectCandidateIsDisabled = true;
}

/// Find the best viable function on this overload set, if it exists.
OverloadingResult BestViableFunction(Sema &S, SourceLocation Loc,
OverloadCandidateSet::iterator& Best);
Expand Down Expand Up @@ -1263,6 +1459,15 @@ class Sema;
DestAS = AS;
}

private:
OverloadingResult ResultForBestCandidate(const iterator &Best);
void CudaExcludeWrongSideCandidates(
Sema &S, SmallVectorImpl<OverloadCandidate *> &Candidates);
OverloadingResult
BestViableFunctionImpl(Sema &S, SourceLocation Loc,
OverloadCandidateSet::iterator &Best);
void PerfectViableFunction(Sema &S, SourceLocation Loc,
OverloadCandidateSet::iterator &Best);
};

bool isBetterOverloadCandidate(Sema &S, const OverloadCandidate &Cand1,
Expand Down Expand Up @@ -1311,6 +1516,21 @@ class Sema;
// parameter.
bool shouldEnforceArgLimit(bool PartialOverloading, FunctionDecl *Function);

inline bool OverloadCandidateSet::shouldDeferTemplateArgumentDeduction(
const LangOptions &Opts) const {
return
// For user defined conversion we need to check against different
// combination of CV qualifiers and look at any explicit specifier, so
// always deduce template candidates.
Kind != CSK_InitByUserDefinedConversion
// When doing code completion, we want to see all the
// viable candidates.
&& Kind != CSK_CodeCompletion
// CUDA may prefer template candidates even when a non-candidate
// is a perfect match
&& !Opts.CUDA;
}

} // namespace clang

#endif // LLVM_CLANG_SEMA_OVERLOAD_H
6 changes: 4 additions & 2 deletions clang/lib/Sema/SemaCodeComplete.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6354,7 +6354,8 @@ SemaCodeCompletion::ProduceCallSignatureHelp(Expr *Fn, ArrayRef<Expr *> Args,
Expr *NakedFn = Fn->IgnoreParenCasts();
// Build an overload candidate set based on the functions we find.
SourceLocation Loc = Fn->getExprLoc();
OverloadCandidateSet CandidateSet(Loc, OverloadCandidateSet::CSK_Normal);
OverloadCandidateSet CandidateSet(Loc,
OverloadCandidateSet::CSK_CodeCompletion);

if (auto ULE = dyn_cast<UnresolvedLookupExpr>(NakedFn)) {
SemaRef.AddOverloadedCallCandidates(ULE, ArgsWithoutDependentTypes,
Expand Down Expand Up @@ -6557,7 +6558,8 @@ QualType SemaCodeCompletion::ProduceConstructorSignatureHelp(
// FIXME: Provide support for variadic template constructors.

if (CRD) {
OverloadCandidateSet CandidateSet(Loc, OverloadCandidateSet::CSK_Normal);
OverloadCandidateSet CandidateSet(Loc,
OverloadCandidateSet::CSK_CodeCompletion);
for (NamedDecl *C : SemaRef.LookupConstructors(CRD)) {
if (auto *FD = dyn_cast<FunctionDecl>(C)) {
// FIXME: we can't yet provide correct signature help for initializer
Expand Down
Loading
Loading