-
Notifications
You must be signed in to change notification settings - Fork 13.6k
[Clang] Don't give up on an unsuccessful function instantiation #126723
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
Conversation
For constexpr function templates, we immediately instantiate them upon reference. However, if the function isn't defined at the point of instantiation, even though it might be defined later, the instantiation would simply fail. This patch corrects the behavior by popping up failed instantiations through PendingInstantiations, so that we are able to instantiate them again in the future (e.g. at the end of TU.)
Compile time tracker: http://llvm-compile-time-tracker.com/compare.php?from=83a8bb363ad578da67a7cb568460a3871ce0ad8b&to=21ca76a13ca62715ce98fb2c8b0361df727769b0 I think the difference is acceptable? |
@llvm/pr-subscribers-clang Author: Younan Zhang (zyn0217) ChangesFor constexpr function templates, we immediately instantiate them upon reference. However, if the function isn't defined at the time of instantiation, even though it might be defined later, the instantiation would forever fail. This patch corrects the behavior by popping up failed instantiations through PendingInstantiations, so that we are able to instantiate them again in the future (e.g. at the end of TU.) Fixes #125747 Full diff: https://github.com/llvm/llvm-project/pull/126723.diff 4 Files Affected:
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 472a0e25adc97..2083a2c2ca40d 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -13588,12 +13588,16 @@ class Sema final : public SemaBase {
class LocalEagerInstantiationScope {
public:
- LocalEagerInstantiationScope(Sema &S) : S(S) {
+ LocalEagerInstantiationScope(Sema &S, bool AtEndOfTU)
+ : S(S), AtEndOfTU(AtEndOfTU) {
SavedPendingLocalImplicitInstantiations.swap(
S.PendingLocalImplicitInstantiations);
}
- void perform() { S.PerformPendingInstantiations(/*LocalOnly=*/true); }
+ void perform() {
+ S.PerformPendingInstantiations(/*LocalOnly=*/true,
+ /*AtEndOfTU=*/AtEndOfTU);
+ }
~LocalEagerInstantiationScope() {
assert(S.PendingLocalImplicitInstantiations.empty() &&
@@ -13604,6 +13608,7 @@ class Sema final : public SemaBase {
private:
Sema &S;
+ bool AtEndOfTU;
std::deque<PendingImplicitInstantiation>
SavedPendingLocalImplicitInstantiations;
};
@@ -13626,8 +13631,8 @@ class Sema final : public SemaBase {
class GlobalEagerInstantiationScope {
public:
- GlobalEagerInstantiationScope(Sema &S, bool Enabled)
- : S(S), Enabled(Enabled) {
+ GlobalEagerInstantiationScope(Sema &S, bool Enabled, bool AtEndOfTU)
+ : S(S), Enabled(Enabled), AtEndOfTU(AtEndOfTU) {
if (!Enabled)
return;
@@ -13641,7 +13646,8 @@ class Sema final : public SemaBase {
void perform() {
if (Enabled) {
S.DefineUsedVTables();
- S.PerformPendingInstantiations();
+ S.PerformPendingInstantiations(/*LocalOnly=*/false,
+ /*AtEndOfTU=*/AtEndOfTU);
}
}
@@ -13656,7 +13662,8 @@ class Sema final : public SemaBase {
S.SavedVTableUses.pop_back();
// Restore the set of pending implicit instantiations.
- if (S.TUKind != TU_Prefix || !S.LangOpts.PCHInstantiateTemplates) {
+ if ((S.TUKind != TU_Prefix || !S.LangOpts.PCHInstantiateTemplates) &&
+ AtEndOfTU) {
assert(S.PendingInstantiations.empty() &&
"PendingInstantiations should be empty before it is discarded.");
S.PendingInstantiations.swap(S.SavedPendingInstantiations.back());
@@ -13675,6 +13682,7 @@ class Sema final : public SemaBase {
private:
Sema &S;
bool Enabled;
+ bool AtEndOfTU;
};
ExplicitSpecifier instantiateExplicitSpecifier(
@@ -13860,7 +13868,8 @@ class Sema final : public SemaBase {
/// Performs template instantiation for all implicit template
/// instantiations we have seen until this point.
- void PerformPendingInstantiations(bool LocalOnly = false);
+ void PerformPendingInstantiations(bool LocalOnly = false,
+ bool AtEndOfTU = true);
TemplateParameterList *
SubstTemplateParams(TemplateParameterList *Params, DeclContext *Owner,
diff --git a/clang/lib/Interpreter/IncrementalParser.cpp b/clang/lib/Interpreter/IncrementalParser.cpp
index e43cea1baf43a..41d6304bd5f65 100644
--- a/clang/lib/Interpreter/IncrementalParser.cpp
+++ b/clang/lib/Interpreter/IncrementalParser.cpp
@@ -41,8 +41,9 @@ llvm::Expected<TranslationUnitDecl *>
IncrementalParser::ParseOrWrapTopLevelDecl() {
// Recover resources if we crash before exiting this method.
llvm::CrashRecoveryContextCleanupRegistrar<Sema> CleanupSema(&S);
- Sema::GlobalEagerInstantiationScope GlobalInstantiations(S, /*Enabled=*/true);
- Sema::LocalEagerInstantiationScope LocalInstantiations(S);
+ Sema::GlobalEagerInstantiationScope GlobalInstantiations(S, /*Enabled=*/true,
+ /*AtEndOfTU=*/true);
+ Sema::LocalEagerInstantiationScope LocalInstantiations(S, /*AtEndOfTU=*/true);
// Add a new PTU.
ASTContext &C = S.getASTContext();
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index d530ed0847ae8..13d157e2a48d1 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -2139,7 +2139,8 @@ Decl *TemplateDeclInstantiator::VisitCXXRecordDecl(CXXRecordDecl *D) {
// DR1484 clarifies that the members of a local class are instantiated as part
// of the instantiation of their enclosing entity.
if (D->isCompleteDefinition() && D->isLocalClass()) {
- Sema::LocalEagerInstantiationScope LocalInstantiations(SemaRef);
+ Sema::LocalEagerInstantiationScope LocalInstantiations(SemaRef,
+ /*AtEndOfTU=*/false);
SemaRef.InstantiateClass(D->getLocation(), Record, D, TemplateArgs,
TSK_ImplicitInstantiation,
@@ -5121,8 +5122,10 @@ void Sema::InstantiateFunctionDefinition(SourceLocation PointOfInstantiation,
// This has to happen before LateTemplateParser below is called, so that
// it marks vtables used in late parsed templates as used.
GlobalEagerInstantiationScope GlobalInstantiations(*this,
- /*Enabled=*/Recursive);
- LocalEagerInstantiationScope LocalInstantiations(*this);
+ /*Enabled=*/Recursive,
+ /*AtEndOfTU=*/AtEndOfTU);
+ LocalEagerInstantiationScope LocalInstantiations(*this,
+ /*AtEndOfTU=*/AtEndOfTU);
// Call the LateTemplateParser callback if there is a need to late parse
// a templated function definition.
@@ -5691,10 +5694,12 @@ void Sema::InstantiateVariableDefinition(SourceLocation PointOfInstantiation,
// If we're performing recursive template instantiation, create our own
// queue of pending implicit instantiations that we will instantiate
// later, while we're still within our own instantiation context.
- GlobalEagerInstantiationScope GlobalInstantiations(*this,
- /*Enabled=*/Recursive);
+ GlobalEagerInstantiationScope GlobalInstantiations(
+ *this,
+ /*Enabled=*/Recursive, /*AtEndOfTU=*/AtEndOfTU);
LocalInstantiationScope Local(*this);
- LocalEagerInstantiationScope LocalInstantiations(*this);
+ LocalEagerInstantiationScope LocalInstantiations(*this,
+ /*AtEndOfTU=*/AtEndOfTU);
// Enter the scope of this instantiation. We don't use
// PushDeclContext because we don't have a scope.
@@ -5791,14 +5796,16 @@ void Sema::InstantiateVariableDefinition(SourceLocation PointOfInstantiation,
// queue of pending implicit instantiations that we will instantiate later,
// while we're still within our own instantiation context.
GlobalEagerInstantiationScope GlobalInstantiations(*this,
- /*Enabled=*/Recursive);
+ /*Enabled=*/Recursive,
+ /*AtEndOfTU=*/AtEndOfTU);
// Enter the scope of this instantiation. We don't use
// PushDeclContext because we don't have a scope.
ContextRAII PreviousContext(*this, Var->getDeclContext());
LocalInstantiationScope Local(*this);
- LocalEagerInstantiationScope LocalInstantiations(*this);
+ LocalEagerInstantiationScope LocalInstantiations(*this,
+ /*AtEndOfTU=*/AtEndOfTU);
VarDecl *OldVar = Var;
if (Def->isStaticDataMember() && !Def->isOutOfLine()) {
@@ -6548,18 +6555,20 @@ NamedDecl *Sema::FindInstantiatedDecl(SourceLocation Loc, NamedDecl *D,
return D;
}
-void Sema::PerformPendingInstantiations(bool LocalOnly) {
- std::deque<PendingImplicitInstantiation> delayedPCHInstantiations;
+void Sema::PerformPendingInstantiations(bool LocalOnly, bool AtEndOfTU) {
+ std::deque<PendingImplicitInstantiation> DelayedImplicitInstantiations;
while (!PendingLocalImplicitInstantiations.empty() ||
(!LocalOnly && !PendingInstantiations.empty())) {
PendingImplicitInstantiation Inst;
+ bool LocalInstantiation = false;
if (PendingLocalImplicitInstantiations.empty()) {
Inst = PendingInstantiations.front();
PendingInstantiations.pop_front();
} else {
Inst = PendingLocalImplicitInstantiations.front();
PendingLocalImplicitInstantiations.pop_front();
+ LocalInstantiation = true;
}
// Instantiate function definitions
@@ -6568,22 +6577,26 @@ void Sema::PerformPendingInstantiations(bool LocalOnly) {
TSK_ExplicitInstantiationDefinition;
if (Function->isMultiVersion()) {
getASTContext().forEachMultiversionedFunctionVersion(
- Function, [this, Inst, DefinitionRequired](FunctionDecl *CurFD) {
+ Function,
+ [this, Inst, DefinitionRequired, AtEndOfTU](FunctionDecl *CurFD) {
InstantiateFunctionDefinition(/*FIXME:*/ Inst.second, CurFD, true,
- DefinitionRequired, true);
+ DefinitionRequired, AtEndOfTU);
if (CurFD->isDefined())
CurFD->setInstantiationIsPending(false);
});
} else {
InstantiateFunctionDefinition(/*FIXME:*/ Inst.second, Function, true,
- DefinitionRequired, true);
+ DefinitionRequired, AtEndOfTU);
if (Function->isDefined())
Function->setInstantiationIsPending(false);
}
// Definition of a PCH-ed template declaration may be available only in the TU.
if (!LocalOnly && LangOpts.PCHInstantiateTemplates &&
TUKind == TU_Prefix && Function->instantiationIsPending())
- delayedPCHInstantiations.push_back(Inst);
+ DelayedImplicitInstantiations.push_back(Inst);
+ else if (!AtEndOfTU && Function->instantiationIsPending() &&
+ !LocalInstantiation)
+ DelayedImplicitInstantiations.push_back(Inst);
continue;
}
@@ -6627,11 +6640,11 @@ void Sema::PerformPendingInstantiations(bool LocalOnly) {
// Instantiate static data member definitions or variable template
// specializations.
InstantiateVariableDefinition(/*FIXME:*/ Inst.second, Var, true,
- DefinitionRequired, true);
+ DefinitionRequired, AtEndOfTU);
}
- if (!LocalOnly && LangOpts.PCHInstantiateTemplates)
- PendingInstantiations.swap(delayedPCHInstantiations);
+ if (!DelayedImplicitInstantiations.empty())
+ PendingInstantiations.swap(DelayedImplicitInstantiations);
}
void Sema::PerformDependentDiagnostics(const DeclContext *Pattern,
diff --git a/clang/test/CodeGenCXX/function-template-specialization.cpp b/clang/test/CodeGenCXX/function-template-specialization.cpp
index 7728f3dc74624..31c78358d014c 100644
--- a/clang/test/CodeGenCXX/function-template-specialization.cpp
+++ b/clang/test/CodeGenCXX/function-template-specialization.cpp
@@ -1,4 +1,5 @@
-// RUN: %clang_cc1 -emit-llvm -triple %itanium_abi_triple %s -o - | FileCheck %s
+// RUN: %clang_cc1 -emit-llvm -Wundefined-func-template -verify -triple %itanium_abi_triple %s -o - | FileCheck %s
+// expected-no-diagnostics
// CHECK-DAG: _ZZN7PR219047GetDataIiEERKibE1i = internal global i32 4
// CHECK-DAG: _ZZN7PR219047GetDataIiEERKibE1i_0 = internal global i32 2
@@ -43,3 +44,19 @@ const int &GetData<int>(bool b) {
return i;
}
}
+
+namespace GH125747 {
+
+template<typename F> constexpr int visit(F f) { return f(0); }
+
+template <class T> int G(T t);
+
+int main() { return visit([](auto s) -> int { return G(s); }); }
+
+template <class T> int G(T t) {
+ return 0;
+}
+
+// CHECK: define {{.*}} @_ZN8GH1257471GIiEEiT_
+
+}
|
Ping |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is missing a changelog entry.
I wonder if AtEndOfTU
should be a bool in Sema instead (that would replace all existing uses of AtEndOfTU
- it would simplify this patch and a few other functions. I don't think we need to do it in this patch though.
The general direction looks good
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry that took a while, I am still coming back from WG21, so I can't look into this throughly yet.
I would look into / want to know about the very specific requirements on hitting the bug, described in the bug report, such as the lambda vs struct thing.
@zyn0217 did you look into, and if so, would you mind including explanations on this part of the issue report: "Curiously, this bug seems to require a lambda -- if the lambda is replaced by an equivalent struct, the bug disappears. Also, a direct call to the lambda does not trigger the bug; it must be indirectly called via another constexpr function." If you didn't look into it, it might be worth as otherwise there could be a simpler fix. |
I looked into that weeks ago, and the reason I remembered was kind of subtle. I'll explain why the fix is necessary later - I'll need a debugger to help me recall that. |
@mizvekov llvm-project/clang/lib/Sema/SemaExpr.cpp Lines 18273 to 18282 in 404f94a
template<typename F> constexpr int visit(F f) { return f(0); }
template <class T> int G(T t);
int main() { return visit([](auto s) -> int { return G(s); }); }
template <class T> int G(T t) {
return 0;
}
So basically, we perform the instantiation as follows.
Technically swapping the first and second branches I mentioned above should work, but the last time I tried that it caused a regression and was reverted." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want @mizvekov to do a run through this as well (as he promised), but from my review, this looks fine.
Yeah, swapping the branches would have been a much simpler fix. What was the regression? Do we have test case for it anywhere? |
|
Kindly ping @mizvekov in case you have more thoughts |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I think this is fine, LGTM.
I would have followed up on the original fix. I think I fixed some of these recursive local lambda bugs in my lambda contextdecl patch, but this might be unrelated otherwise, but still there might be an underlying issue here.
Otherwise, I appreciate this might be too much to follow up for this issue, and the current patch looks good.
…#126723) For constexpr function templates, we immediately instantiate them upon reference. However, if the function isn't defined at the time of instantiation, even though it might be defined later, the instantiation would forever fail. This patch corrects the behavior by popping up failed instantiations through PendingInstantiations, so that we are able to instantiate them again in the future (e.g. at the end of TU.) Fixes llvm#125747
For constexpr function templates, we immediately instantiate them upon reference. However, if the function isn't defined at the time of instantiation, even though it might be defined later, the instantiation would forever fail.
This patch corrects the behavior by popping up failed instantiations through PendingInstantiations, so that we are able to instantiate them again in the future (e.g. at the end of TU.)
Fixes #125747