Skip to content

Improve stack usage to increase recursive initialization depth #88546

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 16 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
4 changes: 4 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,10 @@ Non-comprehensive list of changes in this release
- ``__typeof_unqual__`` is available in all C modes as an extension, which behaves
like ``typeof_unqual`` from C23, similar to ``__typeof__`` and ``typeof``.

- Improved stack usage with C++ initialization code. This allows significantly
more template instantiations before reaching stack exhaustion limits.
Fixes #GH88330

New Compiler Flags
------------------
- ``-fsanitize=implicit-bitfield-conversion`` checks implicit truncation and
Expand Down
6 changes: 4 additions & 2 deletions clang/include/clang/Sema/Initialization.h
Original file line number Diff line number Diff line change
Expand Up @@ -1134,7 +1134,7 @@ class InitializationSequence {
OverloadingResult FailedOverloadResult;

/// The candidate set created when initialization failed.
OverloadCandidateSet FailedCandidateSet;
OverloadCandidateSet *FailedCandidateSet;

/// The incomplete type that caused a failure.
QualType FailedIncompleteType;
Expand Down Expand Up @@ -1403,7 +1403,9 @@ class InitializationSequence {
/// Retrieve a reference to the candidate set when overload
/// resolution fails.
OverloadCandidateSet &getFailedCandidateSet() {
return FailedCandidateSet;
assert(FailedCandidateSet &&
"this should have been allocated in the constructor!");
return *FailedCandidateSet;
}

/// Get the overloading result, for when the initialization
Expand Down
69 changes: 22 additions & 47 deletions clang/include/clang/Sema/Overload.h
Original file line number Diff line number Diff line change
Expand Up @@ -874,7 +874,8 @@ class Sema;
ConversionFixItGenerator Fix;

/// Viable - True to indicate that this overload candidate is viable.
bool Viable : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned Viable : 1;

/// Whether this candidate is the best viable function, or tied for being
/// the best viable function.
Expand All @@ -883,12 +884,14 @@ class Sema;
/// was part of the ambiguity kernel: the minimal non-empty set of viable
/// candidates such that all elements of the ambiguity kernel are better
/// than all viable candidates not in the ambiguity kernel.
bool Best : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned Best : 1;

/// IsSurrogate - True to indicate that this candidate is a
/// surrogate for a conversion to a function pointer or reference
/// (C++ [over.call.object]).
bool IsSurrogate : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned IsSurrogate : 1;

/// IgnoreObjectArgument - True to indicate that the first
/// argument's conversion, which for this function represents the
Expand All @@ -897,18 +900,20 @@ class Sema;
/// implicit object argument is just a placeholder) or a
/// non-static member function when the call doesn't have an
/// object argument.
bool IgnoreObjectArgument : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned IgnoreObjectArgument : 1;

/// True if the candidate was found using ADL.
CallExpr::ADLCallKind IsADLCandidate : 1;
LLVM_PREFERRED_TYPE(CallExpr::ADLCallKind)
unsigned IsADLCandidate : 1;

/// Whether this is a rewritten candidate, and if so, of what kind?
LLVM_PREFERRED_TYPE(OverloadCandidateRewriteKind)
unsigned RewriteKind : 2;

/// FailureKind - The reason why this candidate is not viable.
/// Actually an OverloadFailureKind.
unsigned char FailureKind;
LLVM_PREFERRED_TYPE(OverloadFailureKind)
unsigned FailureKind : 5;

/// The number of call arguments that were explicitly provided,
/// to be used while performing partial ordering of function templates.
Expand Down Expand Up @@ -972,7 +977,9 @@ class Sema;
private:
friend class OverloadCandidateSet;
OverloadCandidate()
: IsSurrogate(false), IsADLCandidate(CallExpr::NotADL), RewriteKind(CRK_None) {}
: IsSurrogate(false),
IsADLCandidate(static_cast<unsigned>(CallExpr::NotADL)),
RewriteKind(CRK_None) {}
};

/// OverloadCandidateSet - A set of overload candidates, used in C++
Expand Down Expand Up @@ -1070,57 +1077,25 @@ class Sema;
};

private:
SmallVector<OverloadCandidate, 16> Candidates;
llvm::SmallPtrSet<uintptr_t, 16> Functions;

// Allocator for ConversionSequenceLists. We store the first few of these
// inline to avoid allocation for small sets.
llvm::BumpPtrAllocator SlabAllocator;
ASTContext &Ctx;
SmallVector<OverloadCandidate, 4> Candidates;
llvm::SmallPtrSet<uintptr_t, 4> Functions;

SourceLocation Loc;
CandidateSetKind Kind;
OperatorRewriteInfo RewriteInfo;

constexpr static unsigned NumInlineBytes =
24 * sizeof(ImplicitConversionSequence);
unsigned NumInlineBytesUsed = 0;
alignas(void *) char InlineSpace[NumInlineBytes];

// Address space of the object being constructed.
LangAS DestAS = LangAS::Default;

/// If we have space, allocates from inline storage. Otherwise, allocates
/// 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,
"Add destruction logic to OverloadCandidateSet::clear().");

unsigned NBytes = sizeof(T) * N;
if (NBytes > NumInlineBytes - NumInlineBytesUsed)
return SlabAllocator.Allocate<T>(N);
char *FreeSpaceStart = InlineSpace + NumInlineBytesUsed;
assert(uintptr_t(FreeSpaceStart) % alignof(void *) == 0 &&
"Misaligned storage!");

NumInlineBytesUsed += NBytes;
return reinterpret_cast<T *>(FreeSpaceStart);
}

void destroyCandidates();

public:
OverloadCandidateSet(SourceLocation Loc, CandidateSetKind CSK,
OverloadCandidateSet(ASTContext &Ctx, SourceLocation Loc,
CandidateSetKind CSK,
OperatorRewriteInfo RewriteInfo = {})
: Loc(Loc), Kind(CSK), RewriteInfo(RewriteInfo) {}
: Ctx(Ctx), Loc(Loc), Kind(CSK), RewriteInfo(RewriteInfo) {}
OverloadCandidateSet(const OverloadCandidateSet &) = delete;
OverloadCandidateSet &operator=(const OverloadCandidateSet &) = delete;
~OverloadCandidateSet() { destroyCandidates(); }
Expand Down Expand Up @@ -1163,7 +1138,7 @@ class Sema;
ConversionSequenceList
allocateConversionSequences(unsigned NumConversions) {
ImplicitConversionSequence *Conversions =
slabAllocate<ImplicitConversionSequence>(NumConversions);
Ctx.Allocate<ImplicitConversionSequence>(NumConversions);

// Construct the new objects.
for (unsigned I = 0; I != NumConversions; ++I)
Expand Down
6 changes: 4 additions & 2 deletions clang/lib/Sema/SemaCodeComplete.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6210,7 +6210,8 @@ QualType Sema::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(Context, Loc,
OverloadCandidateSet::CSK_Normal);

if (auto ULE = dyn_cast<UnresolvedLookupExpr>(NakedFn)) {
AddOverloadedCallCandidates(ULE, ArgsWithoutDependentTypes, CandidateSet,
Expand Down Expand Up @@ -6411,7 +6412,8 @@ QualType Sema::ProduceConstructorSignatureHelp(QualType Type,
// FIXME: Provide support for variadic template constructors.

if (CRD) {
OverloadCandidateSet CandidateSet(Loc, OverloadCandidateSet::CSK_Normal);
OverloadCandidateSet CandidateSet(Context, Loc,
OverloadCandidateSet::CSK_Normal);
for (NamedDecl *C : LookupConstructors(CRD)) {
if (auto *FD = dyn_cast<FunctionDecl>(C)) {
// FIXME: we can't yet provide correct signature help for initializer
Expand Down
3 changes: 2 additions & 1 deletion clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19073,7 +19073,8 @@ static void ComputeSelectedDestructor(Sema &S, CXXRecordDecl *Record) {
}

SourceLocation Loc = Record->getLocation();
OverloadCandidateSet OCS(Loc, OverloadCandidateSet::CSK_Normal);
OverloadCandidateSet OCS(S.getASTContext(), Loc,
OverloadCandidateSet::CSK_Normal);

for (auto *Decl : Record->decls()) {
if (auto *DD = dyn_cast<CXXDestructorDecl>(Decl)) {
Expand Down
5 changes: 3 additions & 2 deletions clang/lib/Sema/SemaDeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8198,7 +8198,8 @@ class DefaultedComparisonAnalyzer
// we've already found there is no viable 'operator<=>' candidate (and are
// considering synthesizing a '<=>' from '==' and '<').
OverloadCandidateSet CandidateSet(
FD->getLocation(), OverloadCandidateSet::CSK_Operator,
S.getASTContext(), FD->getLocation(),
OverloadCandidateSet::CSK_Operator,
OverloadCandidateSet::OperatorRewriteInfo(
OO, FD->getLocation(),
/*AllowRewrittenCandidates=*/!SpaceshipCandidates));
Expand Down Expand Up @@ -17409,7 +17410,7 @@ bool Sema::EvaluateStaticAssertMessageAsString(Expr *Message,
LookupResult MemberLookup(*this, DN, Loc, Sema::LookupMemberName);
LookupQualifiedName(MemberLookup, RD);
Empty = MemberLookup.empty();
OverloadCandidateSet Candidates(MemberLookup.getNameLoc(),
OverloadCandidateSet Candidates(Context, MemberLookup.getNameLoc(),
OverloadCandidateSet::CSK_Normal);
if (MemberLookup.empty())
return std::nullopt;
Expand Down
7 changes: 4 additions & 3 deletions clang/lib/Sema/SemaExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2500,7 +2500,7 @@ bool Sema::DiagnoseEmptyLookup(Scope *S, CXXScopeSpec &SS, LookupResult &R,

// If there's a best viable function among the results, only mention
// that one in the notes.
OverloadCandidateSet Candidates(R.getNameLoc(),
OverloadCandidateSet Candidates(Context, R.getNameLoc(),
OverloadCandidateSet::CSK_Normal);
AddOverloadedCallCandidates(R, ExplicitTemplateArgs, Args, Candidates);
OverloadCandidateSet::iterator Best;
Expand Down Expand Up @@ -2548,7 +2548,7 @@ bool Sema::DiagnoseEmptyLookup(Scope *S, CXXScopeSpec &SS, LookupResult &R,
NamedDecl *ND = Corrected.getFoundDecl();
if (ND) {
if (Corrected.isOverloaded()) {
OverloadCandidateSet OCS(R.getNameLoc(),
OverloadCandidateSet OCS(Context, R.getNameLoc(),
OverloadCandidateSet::CSK_Normal);
OverloadCandidateSet::iterator Best;
for (NamedDecl *CD : Corrected) {
Expand Down Expand Up @@ -6501,7 +6501,8 @@ static TypoCorrection TryTypoCorrectionForCall(Sema &S, Expr *Fn,
Sema::CTK_ErrorRecovery)) {
if (NamedDecl *ND = Corrected.getFoundDecl()) {
if (Corrected.isOverloaded()) {
OverloadCandidateSet OCS(NameLoc, OverloadCandidateSet::CSK_Normal);
OverloadCandidateSet OCS(S.getASTContext(), NameLoc,
OverloadCandidateSet::CSK_Normal);
OverloadCandidateSet::iterator Best;
for (NamedDecl *CD : Corrected) {
if (FunctionDecl *FD = dyn_cast<FunctionDecl>(CD))
Expand Down
6 changes: 3 additions & 3 deletions clang/lib/Sema/SemaExprCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2586,7 +2586,7 @@ static bool resolveAllocationOverload(
Sema &S, LookupResult &R, SourceRange Range, SmallVectorImpl<Expr *> &Args,
bool &PassAlignment, FunctionDecl *&Operator,
OverloadCandidateSet *AlignedCandidates, Expr *AlignArg, bool Diagnose) {
OverloadCandidateSet Candidates(R.getNameLoc(),
OverloadCandidateSet Candidates(S.getASTContext(), R.getNameLoc(),
OverloadCandidateSet::CSK_Normal);
for (LookupResult::iterator Alloc = R.begin(), AllocEnd = R.end();
Alloc != AllocEnd; ++Alloc) {
Expand Down Expand Up @@ -3919,7 +3919,7 @@ static bool resolveBuiltinNewDeleteOverload(Sema &S, CallExpr *TheCall,
R.suppressDiagnostics();

SmallVector<Expr *, 8> Args(TheCall->arguments());
OverloadCandidateSet Candidates(R.getNameLoc(),
OverloadCandidateSet Candidates(S.getASTContext(), R.getNameLoc(),
OverloadCandidateSet::CSK_Normal);
for (LookupResult::iterator FnOvl = R.begin(), FnOvlEnd = R.end();
FnOvl != FnOvlEnd; ++FnOvl) {
Expand Down Expand Up @@ -6488,7 +6488,7 @@ static bool TryClassUnification(Sema &Self, Expr *From, Expr *To,
static bool FindConditionalOverload(Sema &Self, ExprResult &LHS, ExprResult &RHS,
SourceLocation QuestionLoc) {
Expr *Args[2] = { LHS.get(), RHS.get() };
OverloadCandidateSet CandidateSet(QuestionLoc,
OverloadCandidateSet CandidateSet(Self.getASTContext(), QuestionLoc,
OverloadCandidateSet::CSK_Operator);
Self.AddBuiltinOperatorCandidates(OO_Conditional, QuestionLoc, Args,
CandidateSet);
Expand Down
31 changes: 18 additions & 13 deletions clang/lib/Sema/SemaInit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6113,8 +6113,10 @@ static bool TryOCLZeroOpaqueTypeInitialization(Sema &S,
InitializationSequence::InitializationSequence(
Sema &S, const InitializedEntity &Entity, const InitializationKind &Kind,
MultiExprArg Args, bool TopLevelOfInitList, bool TreatUnavailableAsInvalid)
: FailedOverloadResult(OR_Success),
FailedCandidateSet(Kind.getLocation(), OverloadCandidateSet::CSK_Normal) {
: FailedOverloadResult(OR_Success), FailedCandidateSet(nullptr) {
FailedCandidateSet = S.getASTContext().Allocate<OverloadCandidateSet>();
FailedCandidateSet = new (FailedCandidateSet) OverloadCandidateSet(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a future optimization, I wonder if we could defer this allocation until we already know that we've failed? Perhaps in SetOverloadFailure? Not sure I can grok the analysis at the moment to know how possible this is though.

S.getASTContext(), Kind.getLocation(), OverloadCandidateSet::CSK_Normal);
InitializeFrom(S, Entity, Kind, Args, TopLevelOfInitList,
TreatUnavailableAsInvalid);
}
Expand Down Expand Up @@ -6808,7 +6810,8 @@ static ExprResult CopyObject(Sema &S,
// Perform overload resolution using the class's constructors. Per
// C++11 [dcl.init]p16, second bullet for class types, this initialization
// is direct-initialization.
OverloadCandidateSet CandidateSet(Loc, OverloadCandidateSet::CSK_Normal);
OverloadCandidateSet CandidateSet(S.getASTContext(), Loc,
OverloadCandidateSet::CSK_Normal);
DeclContext::lookup_result Ctors = S.LookupConstructors(Class);

OverloadCandidateSet::iterator Best;
Expand Down Expand Up @@ -6949,7 +6952,8 @@ static void CheckCXX98CompatAccessibleCopy(Sema &S,
return;

// Find constructors which would have been considered.
OverloadCandidateSet CandidateSet(Loc, OverloadCandidateSet::CSK_Normal);
OverloadCandidateSet CandidateSet(S.getASTContext(), Loc,
OverloadCandidateSet::CSK_Normal);
DeclContext::lookup_result Ctors =
S.LookupConstructors(cast<CXXRecordDecl>(Record->getDecl()));

Expand Down Expand Up @@ -9735,7 +9739,7 @@ bool InitializationSequence::Diagnose(Sema &S,
switch (FailedOverloadResult) {
case OR_Ambiguous:

FailedCandidateSet.NoteCandidates(
FailedCandidateSet->NoteCandidates(
PartialDiagnosticAt(
Kind.getLocation(),
Failure == FK_UserConversionOverloadFailed
Expand All @@ -9749,7 +9753,8 @@ bool InitializationSequence::Diagnose(Sema &S,
break;

case OR_No_Viable_Function: {
auto Cands = FailedCandidateSet.CompleteCandidates(S, OCD_AllCandidates, Args);
auto Cands =
FailedCandidateSet->CompleteCandidates(S, OCD_AllCandidates, Args);
if (!S.RequireCompleteType(Kind.getLocation(),
DestType.getNonReferenceType(),
diag::err_typecheck_nonviable_condition_incomplete,
Expand All @@ -9759,7 +9764,7 @@ bool InitializationSequence::Diagnose(Sema &S,
<< OnlyArg->getType() << Args[0]->getSourceRange()
<< DestType.getNonReferenceType();

FailedCandidateSet.NoteCandidates(S, Args, Cands);
FailedCandidateSet->NoteCandidates(S, Args, Cands);
break;
}
case OR_Deleted: {
Expand All @@ -9768,7 +9773,7 @@ bool InitializationSequence::Diagnose(Sema &S,
<< Args[0]->getSourceRange();
OverloadCandidateSet::iterator Best;
OverloadingResult Ovl
= FailedCandidateSet.BestViableFunction(S, Kind.getLocation(), Best);
= FailedCandidateSet->BestViableFunction(S, Kind.getLocation(), Best);
if (Ovl == OR_Deleted) {
S.NoteDeletedFunction(Best->Function);
} else {
Expand Down Expand Up @@ -9946,7 +9951,7 @@ bool InitializationSequence::Diagnose(Sema &S,
// bad.
switch (FailedOverloadResult) {
case OR_Ambiguous:
FailedCandidateSet.NoteCandidates(
FailedCandidateSet->NoteCandidates(
PartialDiagnosticAt(Kind.getLocation(),
S.PDiag(diag::err_ovl_ambiguous_init)
<< DestType << ArgsRange),
Expand Down Expand Up @@ -10000,7 +10005,7 @@ bool InitializationSequence::Diagnose(Sema &S,
break;
}

FailedCandidateSet.NoteCandidates(
FailedCandidateSet->NoteCandidates(
PartialDiagnosticAt(
Kind.getLocation(),
S.PDiag(diag::err_ovl_no_viable_function_in_init)
Expand All @@ -10011,7 +10016,7 @@ bool InitializationSequence::Diagnose(Sema &S,
case OR_Deleted: {
OverloadCandidateSet::iterator Best;
OverloadingResult Ovl
= FailedCandidateSet.BestViableFunction(S, Kind.getLocation(), Best);
= FailedCandidateSet->BestViableFunction(S, Kind.getLocation(), Best);
if (Ovl != OR_Deleted) {
S.Diag(Kind.getLocation(), diag::err_ovl_deleted_init)
<< DestType << ArgsRange;
Expand Down Expand Up @@ -10088,7 +10093,7 @@ bool InitializationSequence::Diagnose(Sema &S,
<< Args[0]->getSourceRange();
OverloadCandidateSet::iterator Best;
OverloadingResult Ovl
= FailedCandidateSet.BestViableFunction(S, Kind.getLocation(), Best);
= FailedCandidateSet->BestViableFunction(S, Kind.getLocation(), Best);
(void)Ovl;
assert(Ovl == OR_Success && "Inconsistent overload resolution");
CXXConstructorDecl *CtorDecl = cast<CXXConstructorDecl>(Best->Function);
Expand Down Expand Up @@ -10818,7 +10823,7 @@ QualType Sema::DeduceTemplateSpecializationFromInitializer(
//
// Since we know we're initializing a class type of a type unrelated to that
// of the initializer, this reduces to something fairly reasonable.
OverloadCandidateSet Candidates(Kind.getLocation(),
OverloadCandidateSet Candidates(Context, Kind.getLocation(),
OverloadCandidateSet::CSK_Normal);
OverloadCandidateSet::iterator Best;

Expand Down
2 changes: 1 addition & 1 deletion clang/lib/Sema/SemaLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3481,7 +3481,7 @@ Sema::LookupSpecialMember(CXXRecordDecl *RD, CXXSpecialMemberKind SM,
// Now we perform lookup on the name we computed earlier and do overload
// resolution. Lookup is only performed directly into the class since there
// will always be a (possibly implicit) declaration to shadow any others.
OverloadCandidateSet OCS(LookupLoc, OverloadCandidateSet::CSK_Normal);
OverloadCandidateSet OCS(Context, LookupLoc, OverloadCandidateSet::CSK_Normal);
DeclContext::lookup_result R = RD->lookup(Name);

if (R.empty()) {
Expand Down
Loading