-
Notifications
You must be signed in to change notification settings - Fork 14.5k
[Clang] Reconsider the timing of instantiation of local constexpr lambdas #98758
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
@llvm/pr-subscribers-clang Author: Younan Zhang (zyn0217) ChangesIn the previous patch #95660, we used a strategy of eagerly instantiating local constexpr lambdas. However, that caused a regression in recursive local lambda calls. This patch addressed that by adding an instantiation requirement to the expression constant evaluation, like what we did when deducing the function return type. Closes #98526 (The reduced examples in #97680 seem different, as they don't compile in Clang 18 either. However, #97680 per se should work again with this patch.) Full diff: https://github.com/llvm/llvm-project/pull/98758.diff 2 Files Affected:
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 852344d895ffd..8414f5c7e46c4 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -59,6 +59,7 @@
#include "clang/Sema/Template.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/STLForwardCompat.h"
+#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/ConvertUTF.h"
@@ -5340,20 +5341,31 @@ bool Sema::CheckCXXDefaultArgExpr(SourceLocation CallLoc, FunctionDecl *FD,
struct ImmediateCallVisitor : public RecursiveASTVisitor<ImmediateCallVisitor> {
const ASTContext &Context;
- ImmediateCallVisitor(const ASTContext &Ctx) : Context(Ctx) {}
+ llvm::SmallPtrSetImpl<const FunctionDecl *> *ReferencedFunctions;
+
+ ImmediateCallVisitor(const ASTContext &Ctx,
+ llvm::SmallPtrSetImpl<const FunctionDecl *>
+ *ReferencedFunctions = nullptr)
+ : Context(Ctx), ReferencedFunctions(ReferencedFunctions) {}
bool HasImmediateCalls = false;
bool shouldVisitImplicitCode() const { return true; }
bool VisitCallExpr(CallExpr *E) {
- if (const FunctionDecl *FD = E->getDirectCallee())
+ if (const FunctionDecl *FD = E->getDirectCallee()) {
HasImmediateCalls |= FD->isImmediateFunction();
+ if (ReferencedFunctions)
+ ReferencedFunctions->insert(FD);
+ }
return RecursiveASTVisitor<ImmediateCallVisitor>::VisitStmt(E);
}
bool VisitCXXConstructExpr(CXXConstructExpr *E) {
- if (const FunctionDecl *FD = E->getConstructor())
+ if (const FunctionDecl *FD = E->getConstructor()) {
HasImmediateCalls |= FD->isImmediateFunction();
+ if (ReferencedFunctions)
+ ReferencedFunctions->insert(FD);
+ }
return RecursiveASTVisitor<ImmediateCallVisitor>::VisitStmt(E);
}
@@ -16983,6 +16995,30 @@ Sema::VerifyIntegerConstantExpression(Expr *E, llvm::APSInt *Result,
SmallVector<PartialDiagnosticAt, 8> Notes;
EvalResult.Diag = &Notes;
+ // Check if the expression refers to local functions yet to be instantiated.
+ // If so, instantiate them now, as the constant evaluation requires the
+ // function definition.
+ if (!PendingLocalImplicitInstantiations.empty()) {
+ llvm::SmallPtrSet<const FunctionDecl *, 4> ReferencedFunctions;
+ ImmediateCallVisitor V(getASTContext(), &ReferencedFunctions);
+ V.TraverseStmt(E);
+
+ auto Pred = [&](PendingImplicitInstantiation Pair) {
+ ValueDecl *VD = Pair.first;
+ return isa<FunctionDecl>(VD) &&
+ ReferencedFunctions.contains(cast<FunctionDecl>(VD));
+ };
+ // Workaround: A lambda with captures cannot be copy-assigned, which is
+ // required by llvm::make_filter_range().
+ llvm::function_ref<bool(PendingImplicitInstantiation)> PredRef = Pred;
+
+ auto R =
+ llvm::make_filter_range(PendingLocalImplicitInstantiations, PredRef);
+ LocalEagerInstantiationScope InstantiateReferencedLocalFunctions(*this);
+ PendingLocalImplicitInstantiations = {R.begin(), R.end()};
+ InstantiateReferencedLocalFunctions.perform();
+ }
+
// Try to evaluate the expression, and produce diagnostics explaining why it's
// not a constant expression as a side-effect.
bool Folded =
@@ -17938,17 +17974,16 @@ void Sema::MarkFunctionReferenced(SourceLocation Loc, FunctionDecl *Func,
if (FirstInstantiation || TSK != TSK_ImplicitInstantiation ||
Func->isConstexpr()) {
- if (Func->isConstexpr())
+ if (isa<CXXRecordDecl>(Func->getDeclContext()) &&
+ cast<CXXRecordDecl>(Func->getDeclContext())->isLocalClass() &&
+ CodeSynthesisContexts.size())
+ PendingLocalImplicitInstantiations.push_back(
+ std::make_pair(Func, PointOfInstantiation));
+ else if (Func->isConstexpr())
// Do not defer instantiations of constexpr functions, to avoid the
// expression evaluator needing to call back into Sema if it sees a
// call to such a function.
InstantiateFunctionDefinition(PointOfInstantiation, Func);
- else if (isa<CXXRecordDecl>(Func->getDeclContext()) &&
- cast<CXXRecordDecl>(Func->getDeclContext())
- ->isLocalClass() &&
- CodeSynthesisContexts.size())
- PendingLocalImplicitInstantiations.push_back(
- std::make_pair(Func, PointOfInstantiation));
else {
Func->setInstantiationIsPending(true);
PendingInstantiations.push_back(
diff --git a/clang/test/SemaTemplate/instantiate-local-class.cpp b/clang/test/SemaTemplate/instantiate-local-class.cpp
index 7eee131e28d60..bee4cbaf33dd7 100644
--- a/clang/test/SemaTemplate/instantiate-local-class.cpp
+++ b/clang/test/SemaTemplate/instantiate-local-class.cpp
@@ -532,4 +532,30 @@ int main() {
} // namespace GH35052
+namespace GH98526 {
+
+template <typename F> struct recursive_lambda {
+ template <typename... Args> auto operator()(Args &&...args) const {
+ return fn(*this, args...);
+ }
+ F fn;
+};
+
+template <typename F> recursive_lambda(F) -> recursive_lambda<F>;
+
+struct Tree {
+ Tree *left, *right;
+};
+
+int sumSize(Tree *tree) {
+ auto accumulate =
+ recursive_lambda{[&](auto &self_fn, Tree *element_node) -> int {
+ return 1 + self_fn(tree->left) + self_fn(tree->right);
+ }};
+
+ return accumulate(tree);
+}
+
+} // namespace GH98526
+
#endif
|
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.
Thanks a lot for working on this regression
ImmediateCallVisitor(const ASTContext &Ctx, | ||
llvm::SmallPtrSetImpl<const FunctionDecl *> | ||
*ReferencedFunctions = nullptr) | ||
: Context(Ctx), ReferencedFunctions(ReferencedFunctions) {} |
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.
If we are going to reuse this object, we should rename it - or maybe we need a separate class
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.
A separate class would avoid the pointer to SmallPtrSetImpl, which is not great
// Check if the expression refers to local functions yet to be instantiated. | ||
// If so, instantiate them now, as the constant evaluation requires the | ||
// function definition. | ||
if (!PendingLocalImplicitInstantiations.empty()) { |
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.
Can you explain why we check for not having pending instantiations ?
if (isa<CXXRecordDecl>(Func->getDeclContext()) && | ||
cast<CXXRecordDecl>(Func->getDeclContext())->isLocalClass() && | ||
CodeSynthesisContexts.size()) | ||
PendingLocalImplicitInstantiations.push_back( | ||
std::make_pair(Func, PointOfInstantiation)); |
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 think this would benefit from a comment
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.
Curiously, if we move the call to lambda wrapper out of the function, then it would compile even on trunk -- https://gcc.godbolt.org/z/aEcajcEvE
I probably overlooked something here.
EDIT: https://gcc.godbolt.org/z/zzGEEWd3h: if we change accumulate(tree)
to accumulate(nullptr)
, it will also compile within the function ?!
@cor3ntin I'm stuck here for hours and I think I need some help. So let me explain what happens before #95660. Consider the example that doesn't compile before #95660: template <typename F> constexpr int func(F f) {
constexpr bool y = f(1UL);
return y;
}
int main() {
auto predicate = [](auto v) /*implicit constexpr*/ -> bool {
return v == 1;
};
return func(predicate);
} we would encounter an error saying that We won't have the corresponding lambda bodies as we have deferred the instantiation until the end of the function definition. Therefore, when we instantiate If I understand correctly, our constant evaluator is designed to be oblivious of However, as you can see, this also subtly caused such a regression. Again, I'll demonstrate an example: template <typename F> struct recursive_lambda {
template <typename... Args> auto operator()(Args &&...args) const {
return fn(*this, args...);
}
F fn;
};
template <typename F> recursive_lambda(F) -> recursive_lambda<F>;
struct Tree {
Tree *left, *right;
};
int sumSize(Tree *tree) {
auto accumulate =
recursive_lambda{[&](auto &self_fn, Tree *element_node) -> int {
return 1 + self_fn(tree->left) + self_fn(tree->right);
}};
accumulate(tree);
} In this example, we want to deduce the return type of But what will happen if we defer that lambda's instantiation? That means we don't have to instantiate So far, I have thought of some approaches:
This has afflicted me for half the night, and I really need some guidance! |
Finally, I decide to revert #95660 and close this PR for now. Regarding issues associated with #95660, I realized they are actually part of a bigger problem in constant evaluation. As discussed in #59966, it might be beneficial to implement on-demand template instantiation for constant evaluation. That way, we don't have to touch the logic of deferral instantiation in |
I haven't had time to look in the issue in details, but this might be the best solution. |
In the previous patch #95660, we used a strategy of eagerly instantiating local constexpr lambdas. However, that caused a regression in recursive local lambda calls.
This patch addresses that by adding an instantiation requirement to the expression constant evaluation, like what we did when deducing the function return type.
Closes #98526
(The reduced examples in #97680 seem different, as they don't compile in Clang 18 either. However, #97680 per se should work again with this patch.)