Skip to content
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

[Clang] Eagerly instantiate used constexpr function upon definition. #73463

Merged
merged 4 commits into from
Nov 30, 2023

Conversation

cor3ntin
Copy link
Contributor

Despite CWG2497 not being resolved, it is reasonable to expect the following code to compile (and which is supported by other compilers)

  template<typename T> constexpr T f();
  constexpr int g() { return f<int>(); } // #1
  template<typename T> constexpr T f() { return 123; }
  int k[g()];
  // #2

To that end, we eagerly instantiate all referenced specializations of constexpr functions when they are defined.

We maintain a map of (pattern, [instantiations]) independant of PendingInstantiations to avoid having to iterate that list after each function definition.

We should apply the same logic to constexpr variables, but I wanted to keep the PR small.

Fixes #73232

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Nov 26, 2023
@llvmbot
Copy link
Member

llvmbot commented Nov 26, 2023

@llvm/pr-subscribers-clang-modules

@llvm/pr-subscribers-clang

Author: cor3ntin (cor3ntin)

Changes

Despite CWG2497 not being resolved, it is reasonable to expect the following code to compile (and which is supported by other compilers)

  template&lt;typename T&gt; constexpr T f();
  constexpr int g() { return f&lt;int&gt;(); } // #<!-- -->1
  template&lt;typename T&gt; constexpr T f() { return 123; }
  int k[g()];
  // #<!-- -->2

To that end, we eagerly instantiate all referenced specializations of constexpr functions when they are defined.

We maintain a map of (pattern, [instantiations]) independant of PendingInstantiations to avoid having to iterate that list after each function definition.

We should apply the same logic to constexpr variables, but I wanted to keep the PR small.

Fixes #73232


Full diff: https://github.com/llvm/llvm-project/pull/73463.diff

7 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+5)
  • (modified) clang/include/clang/Sema/Sema.h (+11)
  • (modified) clang/lib/Sema/SemaDecl.cpp (+3)
  • (modified) clang/lib/Sema/SemaExpr.cpp (+7-2)
  • (modified) clang/lib/Sema/SemaTemplateInstantiateDecl.cpp (+17)
  • (modified) clang/test/SemaCXX/cxx2b-consteval-propagate.cpp (+5-3)
  • (added) clang/test/SemaTemplate/instantiate-used-constexpr-function.cpp (+30)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 29a06d0f713f588..b34b66ec7dcabc2 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -762,6 +762,11 @@ Bug Fixes to C++ Support
   completes (except deduction guides). Fixes:
   (`#59827 <https://github.com/llvm/llvm-project/issues/59827>`_)
 
+- Clang now immediately instantiates function template specializations
+  at the end of the definition of the corresponding function template
+  when the definition appears after the first point of instantiation.
+  (`#73232 <https://github.com/llvm/llvm-project/issues/73232>`_)
+
 Bug Fixes to AST Handling
 ^^^^^^^^^^^^^^^^^^^^^^^^^
 - Fixed an import failure of recursive friend class template.
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index f7c9d0e2e6412b7..091e1e3b4c1fd64 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -59,6 +59,7 @@
 #include "clang/Sema/TypoCorrection.h"
 #include "clang/Sema/Weak.h"
 #include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/SetVector.h"
 #include "llvm/ADT/SmallBitVector.h"
 #include "llvm/ADT/SmallPtrSet.h"
@@ -10078,6 +10079,12 @@ class Sema final {
   /// but have not yet been performed.
   std::deque<PendingImplicitInstantiation> PendingInstantiations;
 
+  /// Track constexpr functions referenced before they are (lexically) defined.
+  /// The key is the pattern, associated with a list of specialisations that
+  /// need to be instantiated when the pattern is defined.
+  llvm::DenseMap<NamedDecl *, SmallVector<NamedDecl *>>
+      PendingInstantiationsOfConstexprEntities;
+
   /// Queue of implicit template instantiations that cannot be performed
   /// eagerly.
   SmallVector<PendingImplicitInstantiation, 1> LateParsedInstantiations;
@@ -10396,6 +10403,10 @@ class Sema final {
                                      bool Recursive = false,
                                      bool DefinitionRequired = false,
                                      bool AtEndOfTU = false);
+
+  void InstantiateFunctionTemplateSpecializations(
+      SourceLocation PointOfInstantiation, FunctionDecl *Template);
+
   VarTemplateSpecializationDecl *BuildVarTemplateInstantiation(
       VarTemplateDecl *VarTemplate, VarDecl *FromVar,
       const TemplateArgumentList &TemplateArgList,
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 23dd8ae15c16583..1030ba0d21b1906 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -16255,6 +16255,9 @@ Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body,
   if (FD && !FD->isDeleted())
     checkTypeSupport(FD->getType(), FD->getLocation(), FD);
 
+  if (FD && FD->isConstexpr() && FD->isTemplated())
+    InstantiateFunctionTemplateSpecializations(FD->getEndLoc(), FD);
+
   return dcl;
 }
 
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index fc39d6149c1cc65..37b0d0eed35845c 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -19047,12 +19047,17 @@ void Sema::MarkFunctionReferenced(SourceLocation Loc, FunctionDecl *Func,
               CodeSynthesisContexts.size())
             PendingLocalImplicitInstantiations.push_back(
                 std::make_pair(Func, PointOfInstantiation));
-          else if (Func->isConstexpr())
+          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 (!Func->isDefined()) {
+              PendingInstantiationsOfConstexprEntities
+                  [Func->getTemplateInstantiationPattern()->getCanonicalDecl()]
+                      .push_back(Func);
+            }
+          } else {
             Func->setInstantiationIsPending(true);
             PendingInstantiations.push_back(
                 std::make_pair(Func, PointOfInstantiation));
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 08f4ba00fc9f7de..9afbf139a1c2b88 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -6481,6 +6481,23 @@ void Sema::PerformPendingInstantiations(bool LocalOnly) {
     PendingInstantiations.swap(delayedPCHInstantiations);
 }
 
+// Instantiate all referenced specializations of the given function template
+// definition. This make sure that function template that are defined after the
+// point of instantiation of their used can be evaluated after they are defined.
+// see CWG2497.
+void Sema::InstantiateFunctionTemplateSpecializations(
+    SourceLocation PointOfInstantiation, FunctionDecl *Tpl) {
+  auto It =
+      PendingInstantiationsOfConstexprEntities.find(Tpl->getCanonicalDecl());
+  if (It == PendingInstantiationsOfConstexprEntities.end())
+    return;
+  for (NamedDecl *Fun : It->second) {
+    InstantiateFunctionDefinition(PointOfInstantiation,
+                                  cast<FunctionDecl>(Fun));
+  }
+  PendingInstantiationsOfConstexprEntities.erase(It);
+}
+
 void Sema::PerformDependentDiagnostics(const DeclContext *Pattern,
                        const MultiLevelTemplateArgumentList &TemplateArgs) {
   for (auto *DD : Pattern->ddiags()) {
diff --git a/clang/test/SemaCXX/cxx2b-consteval-propagate.cpp b/clang/test/SemaCXX/cxx2b-consteval-propagate.cpp
index 531a62622873357..a2b4039a31db778 100644
--- a/clang/test/SemaCXX/cxx2b-consteval-propagate.cpp
+++ b/clang/test/SemaCXX/cxx2b-consteval-propagate.cpp
@@ -105,13 +105,15 @@ template <typename T>
 constexpr int f(T t);
 
 auto a = &f<char>;
-auto b = &f<int>; // expected-error {{immediate function 'f<int>' used before it is defined}} \
-                  // expected-note {{in instantiation of function template specialization}}
+auto b = &f<int>; // expected-error {{immediate function 'f<int>' used before it is defined}}
 
 template <typename T>
 constexpr int f(T t) { // expected-note {{'f<int>' defined here}}
     return id(t); // expected-note {{'f<int>' is an immediate function because its body contains a call to a consteval function 'id' and that call is not a constant expression}}
-}
+} // expected-note {{in instantiation of function template specialization}}
+
+
+
 }
 
 namespace constructors {
diff --git a/clang/test/SemaTemplate/instantiate-used-constexpr-function.cpp b/clang/test/SemaTemplate/instantiate-used-constexpr-function.cpp
new file mode 100644
index 000000000000000..57353d681ac7708
--- /dev/null
+++ b/clang/test/SemaTemplate/instantiate-used-constexpr-function.cpp
@@ -0,0 +1,30 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+// expected-no-diagnostics
+
+namespace GH73232  {
+
+template <typename _CharT>
+struct basic_string {
+  constexpr void _M_construct();
+  constexpr basic_string() {
+    _M_construct();
+  }
+};
+
+basic_string<char> a;
+
+template <typename _CharT>
+constexpr void basic_string<_CharT>::_M_construct(){}
+constexpr basic_string<char> z{};
+
+template <typename T>
+constexpr void g(T);
+
+constexpr int f() { g(0); return 0; }
+
+template <typename T>
+constexpr void g(T) {}
+
+constexpr int z = f();
+
+}

@tbaederr
Copy link
Contributor

Does this have any compile-time impact?

@cor3ntin
Copy link
Contributor Author

Does this have any compile-time impact?

Nope, should be ~free. It does add 1 DenseMap lookup per constexpr function definition, which ought to be negligible.

@zygoloid
Copy link
Collaborator

I think the new map should be unnecessary: FunctionTemplateDecl and VarTemplateDecl already have a list of specializations that you can walk and check the point of instantiation and template specialization kind to see if there's a pending instantiation.

If you want/need to keep the map approach, you'll need to also deal with the case where the template is declared in a PCH and then defined somewhere that includes the PCH, which will mean serializing and deserializing the map contents.

@cor3ntin
Copy link
Contributor Author

I think the new map should be unnecessary: FunctionTemplateDecl and VarTemplateDecl already have a list of specializations that you can walk and check the point of instantiation and template specialization kind to see if there's a pending instantiation.

I don't think that's viable for non-template but templated entities like member function in a class template. Am I missing something?

If you want/need to keep the map approach, you'll need to also deal with the case where the template is declared in a PCH and then defined somewhere that includes the PCH, which will mean serializing and deserializing the map contents.

I did not consider that :(

@zygoloid
Copy link
Collaborator

I think the new map should be unnecessary: FunctionTemplateDecl and VarTemplateDecl already have a list of specializations that you can walk and check the point of instantiation and template specialization kind to see if there's a pending instantiation.

I don't think that's viable for non-template but templated entities like member function in a class template. Am I missing something?

Hmm, good point, we don't have a way to walk the specializations of a templated function that's not a function template. (You'd need to walk all the instantiations of the enclosing template and check to see if the corresponding member function has a pending instantiation, or something like that, which sounds painful. Friends defined in class templates would probably be especially problematic.)

Let's use your map approach then :-)

@llvmbot llvmbot added the clang:modules C++20 modules and Clang Header Modules label Nov 28, 2023
@cor3ntin
Copy link
Contributor Author

@zygoloid It took me a while, but I think I got it; what do you think of direction ? (description in the last commit message)
It appears I am doing something a bit novel, but I couldn't think of a better way.

Copy link
Collaborator

@zygoloid zygoloid left a comment

Choose a reason for hiding this comment

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

Thanks!

clang/include/clang/Sema/ExternalSemaSource.h Outdated Show resolved Hide resolved
clang/lib/Sema/SemaTemplateInstantiateDecl.cpp Outdated Show resolved Hide resolved
clang/lib/Sema/SemaTemplateInstantiateDecl.cpp Outdated Show resolved Hide resolved
clang/lib/Serialization/ASTReader.cpp Show resolved Hide resolved
clang/lib/Serialization/ASTWriter.cpp Outdated Show resolved Hide resolved
clang/lib/Sema/SemaTemplateInstantiateDecl.cpp Outdated Show resolved Hide resolved
Comment on lines 115 to 117



Copy link
Collaborator

Choose a reason for hiding this comment

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

Revert these added blank lines.

Copy link
Collaborator

@zygoloid zygoloid left a comment

Choose a reason for hiding this comment

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

Thanks! I assume you'll also be looking at the variable case (perhaps as a separate commit)?

Copy link
Collaborator

@shafik shafik left a comment

Choose a reason for hiding this comment

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

Thank you, LGTM

Despite CWG2497 not being resolved, it is reasonable to expect the following
code to compile (and which is supported by other compilers)

```cpp
  template<typename T> constexpr T f();
  constexpr int g() { return f<int>(); } // #1
  template<typename T> constexpr T f() { return 123; }
  int k[g()];
  // #2
```

To that end, we eagerly instantiate all referenced
specializations of constexpr functions when they are defined.

We maintain a map of (pattern, [instantiations]) independant of
`PendingInstantiations` to avoid having to iterate that list after
each function definition.

We should apply the same logic to constexpr variables,
but I wanted to keep the PR small.

Fixes llvm#73232
I wanted to avoid loading eagerly all function templates
and their instantiations -
even if ultimately `PendingInstantiations` - which contains the same information
is going to be loaded at the end of the TU.

So instead, when we find the definition of a constexpr
function template we load the associated instantiation from the
external modules at that point.

I'm not sure there is a better way to get to a DeclID from a Decl*
than to iterate over all redeclarations?

I still need to cleanup but I'd like feedback on direction first!
@cor3ntin
Copy link
Contributor Author

Thanks! I assume you'll also be looking at the variable case (perhaps as a separate commit)?

I'll try to get to it, yes!
Thanks for the review

@cor3ntin cor3ntin merged commit 030047c into llvm:main Nov 30, 2023
4 checks passed
@cor3ntin cor3ntin deleted the corentin/gh73232 branch November 30, 2023 07:45
cor3ntin added a commit that referenced this pull request Dec 2, 2023
…nition. (#73463)"

This reverts commit 030047c.

Breaks Qt and is inconsistent with GCC.

See the following issue for details:

Fixes #74069
// Build a record containing all pending instantiations of constexpr
// entities.
RecordData PendingInstantiationsOfConstexprEntities;
for (const auto &I : SemaRef.PendingInstantiationsOfConstexprEntities) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

In case this will be relanded in some form:

This loop uses arbitrary hashtable order, leading to nondeterministic PCM output.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:modules C++20 modules and Clang Header Modules clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Clang reports an undefined function that has been defined
6 participants