Skip to content

Commit

Permalink
[Clang] Eagerly instantiate used constexpr function upon definition. (#…
Browse files Browse the repository at this point in the history
…73463)

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]) independent 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
  • Loading branch information
cor3ntin committed Nov 30, 2023
1 parent 8a66510 commit 030047c
Show file tree
Hide file tree
Showing 14 changed files with 165 additions and 2 deletions.
5 changes: 5 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,11 @@ Bug Fixes to C++ Support
- Fix crash when parsing nested requirement. Fixes:
(`#73112 <https://github.com/llvm/llvm-project/issues/73112>`_)

- 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.
Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Sema/ExternalSemaSource.h
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,9 @@ class ExternalSemaSource : public ExternalASTSource {
SmallVectorImpl<std::pair<ValueDecl *,
SourceLocation> > &Pending) {}

virtual void ReadPendingInstantiationsOfConstexprEntity(
const NamedDecl *D, llvm::SmallSetVector<NamedDecl *, 4> &Decls){};

/// Read the set of late parsed template functions for this source.
///
/// The external source should insert its own late parsed template functions
Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Sema/MultiplexExternalSemaSource.h
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,9 @@ class MultiplexExternalSemaSource : public ExternalSemaSource {
void ReadPendingInstantiations(
SmallVectorImpl<std::pair<ValueDecl*, SourceLocation> >& Pending) override;

virtual void ReadPendingInstantiationsOfConstexprEntity(
const NamedDecl *D, llvm::SmallSetVector<NamedDecl *, 4> &Decls) override;

/// Read the set of late parsed template functions for this source.
///
/// The external source should insert its own late parsed template functions
Expand Down
10 changes: 10 additions & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -10087,6 +10088,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;
Expand Down Expand Up @@ -10405,6 +10412,9 @@ class Sema final {
bool Recursive = false,
bool DefinitionRequired = false,
bool AtEndOfTU = false);

void PerformPendingInstantiationsOfConstexprFunctions(FunctionDecl *Template);

VarTemplateSpecializationDecl *BuildVarTemplateInstantiation(
VarTemplateDecl *VarTemplate, VarDecl *FromVar,
const TemplateArgumentList &TemplateArgList,
Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/Serialization/ASTBitCodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,10 @@ enum ASTRecordTypes {
/// Record code for an unterminated \#pragma clang assume_nonnull begin
/// recorded in a preamble.
PP_ASSUME_NONNULL_LOC = 67,

/// Record code for constexpr templated entities that have been used but not
/// yet instantiated.
PENDING_INSTANTIATIONS_OF_CONSTEXPR_ENTITIES = 68,
};

/// Record types used within a source manager block.
Expand Down
6 changes: 6 additions & 0 deletions clang/include/clang/Serialization/ASTReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,9 @@ class ASTReader
/// is the instantiation location.
SmallVector<serialization::DeclID, 64> PendingInstantiations;

llvm::DenseMap<serialization::DeclID, std::set<serialization::DeclID>>
PendingInstantiationsOfConstexprEntities;

//@}

/// \name DiagnosticsEngine-relevant special data
Expand Down Expand Up @@ -2101,6 +2104,9 @@ class ASTReader
SmallVectorImpl<std::pair<ValueDecl *,
SourceLocation>> &Pending) override;

virtual void ReadPendingInstantiationsOfConstexprEntity(
const NamedDecl *D, llvm::SmallSetVector<NamedDecl *, 4> &Decls) override;

void ReadLateParsedTemplates(
llvm::MapVector<const FunctionDecl *, std::unique_ptr<LateParsedTemplate>>
&LPTMap) override;
Expand Down
6 changes: 6 additions & 0 deletions clang/lib/Sema/MultiplexExternalSemaSource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,12 @@ void MultiplexExternalSemaSource::ReadPendingInstantiations(
Sources[i]->ReadPendingInstantiations(Pending);
}

void MultiplexExternalSemaSource::ReadPendingInstantiationsOfConstexprEntity(
const NamedDecl *D, llvm::SmallSetVector<NamedDecl *, 4> &Decls) {
for (size_t i = 0; i < Sources.size(); ++i)
Sources[i]->ReadPendingInstantiationsOfConstexprEntity(D, Decls);
};

void MultiplexExternalSemaSource::ReadLateParsedTemplates(
llvm::MapVector<const FunctionDecl *, std::unique_ptr<LateParsedTemplate>>
&LPTMap) {
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16275,6 +16275,9 @@ Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body,
if (FD && !FD->isDeleted())
checkTypeSupport(FD->getType(), FD->getLocation(), FD);

if (FD && FD->isConstexpr() && FD->isTemplated())
PerformPendingInstantiationsOfConstexprFunctions(FD);

return dcl;
}

Expand Down
9 changes: 7 additions & 2 deletions clang/lib/Sema/SemaExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19053,12 +19053,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));
Expand Down
28 changes: 28 additions & 0 deletions clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6495,6 +6495,34 @@ void Sema::PerformPendingInstantiations(bool LocalOnly) {
PendingInstantiations.swap(delayedPCHInstantiations);
}

// Instantiate all referenced specializations of the given function template
// definition. This make sure that constexpr function templates that are defined
// after the point of instantiation of their use can be evaluated after they
// are defined. see CWG2497.
void Sema::PerformPendingInstantiationsOfConstexprFunctions(FunctionDecl *Tpl) {

auto InstantiateAll = [&](const auto &Range) {
for (NamedDecl *D : Range) {
FunctionDecl *Fun = cast<FunctionDecl>(D);
InstantiateFunctionDefinition(Fun->getPointOfInstantiation(), Fun);
}
};

auto It =
PendingInstantiationsOfConstexprEntities.find(Tpl->getCanonicalDecl());
if (It != PendingInstantiationsOfConstexprEntities.end()) {
auto Decls = std::move(It->second);
PendingInstantiationsOfConstexprEntities.erase(It);
InstantiateAll(Decls);
}

llvm::SmallSetVector<NamedDecl *, 4> Decls;
if (ExternalSource) {
ExternalSource->ReadPendingInstantiationsOfConstexprEntity(Tpl, Decls);
InstantiateAll(Decls);
}
}

void Sema::PerformDependentDiagnostics(const DeclContext *Pattern,
const MultiLevelTemplateArgumentList &TemplateArgs) {
for (auto *DD : Pattern->ddiags()) {
Expand Down
27 changes: 27 additions & 0 deletions clang/lib/Serialization/ASTReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3709,6 +3709,19 @@ llvm::Error ASTReader::ReadASTBlock(ModuleFile &F,
}
break;

case PENDING_INSTANTIATIONS_OF_CONSTEXPR_ENTITIES:
if (Record.size() % 2 != 0)
return llvm::createStringError(
std::errc::illegal_byte_sequence,
"Invalid PENDING_INSTANTIATIONS_OF_CONSTEXPR_ENTITIES block");

for (unsigned I = 0, N = Record.size(); I != N; /* in loop */) {
DeclID Key = getGlobalDeclID(F, Record[I++]);
DeclID Value = getGlobalDeclID(F, Record[I++]);
PendingInstantiationsOfConstexprEntities[Key].insert(Value);
}
break;

case SEMA_DECL_REFS:
if (Record.size() != 3)
return llvm::createStringError(std::errc::illegal_byte_sequence,
Expand Down Expand Up @@ -8718,6 +8731,20 @@ void ASTReader::ReadPendingInstantiations(
PendingInstantiations.clear();
}

void ASTReader::ReadPendingInstantiationsOfConstexprEntity(
const NamedDecl *D, llvm::SmallSetVector<NamedDecl *, 4> &Decls) {
for (auto *Redecl : D->redecls()) {
if (!Redecl->isFromASTFile())
continue;
DeclID Id = Redecl->getGlobalID();
auto It = PendingInstantiationsOfConstexprEntities.find(Id);
if (It == PendingInstantiationsOfConstexprEntities.end())
continue;
for (DeclID InstantiationId : It->second)
Decls.insert(cast<NamedDecl>(GetDecl(InstantiationId)));
}
}

void ASTReader::ReadLateParsedTemplates(
llvm::MapVector<const FunctionDecl *, std::unique_ptr<LateParsedTemplate>>
&LPTMap) {
Expand Down
16 changes: 16 additions & 0 deletions clang/lib/Serialization/ASTWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,7 @@ void ASTWriter::WriteBlockInfoBlock() {
RECORD(SEMA_DECL_REFS);
RECORD(WEAK_UNDECLARED_IDENTIFIERS);
RECORD(PENDING_IMPLICIT_INSTANTIATIONS);
RECORD(PENDING_INSTANTIATIONS_OF_CONSTEXPR_ENTITIES);
RECORD(UPDATE_VISIBLE);
RECORD(DECL_UPDATE_OFFSETS);
RECORD(DECL_UPDATES);
Expand Down Expand Up @@ -4836,6 +4837,16 @@ ASTFileSignature ASTWriter::WriteASTCore(Sema &SemaRef, StringRef isysroot,
assert(SemaRef.PendingLocalImplicitInstantiations.empty() &&
"There are local ones at end of translation unit!");

// Build a record containing all pending instantiations of constexpr
// entities.
RecordData PendingInstantiationsOfConstexprEntities;
for (const auto &I : SemaRef.PendingInstantiationsOfConstexprEntities) {
for (const auto &Elem : I.second) {
AddDeclRef(I.first, PendingInstantiationsOfConstexprEntities);
AddDeclRef(Elem, PendingInstantiationsOfConstexprEntities);
}
}

// Build a record containing some declaration references.
RecordData SemaDeclRefs;
if (SemaRef.StdNamespace || SemaRef.StdBadAlloc || SemaRef.StdAlignValT) {
Expand Down Expand Up @@ -5153,6 +5164,11 @@ ASTFileSignature ASTWriter::WriteASTCore(Sema &SemaRef, StringRef isysroot,
if (!PendingInstantiations.empty())
Stream.EmitRecord(PENDING_IMPLICIT_INSTANTIATIONS, PendingInstantiations);

// Write the record containing pending instantiations of constexpr entities.
if (!PendingInstantiationsOfConstexprEntities.empty())
Stream.EmitRecord(PENDING_INSTANTIATIONS_OF_CONSTEXPR_ENTITIES,
PendingInstantiationsOfConstexprEntities);

// Write the record containing declaration references of Sema.
if (!SemaDeclRefs.empty())
Stream.EmitRecord(SEMA_DECL_REFS, SemaDeclRefs);
Expand Down
17 changes: 17 additions & 0 deletions clang/test/PCH/instantiate-used-constexpr-function.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// RUN: %clang_cc1 -std=c++2a -emit-pch %s -o %t
// RUN: %clang_cc1 -std=c++2a -include-pch %t -verify %s

// expected-no-diagnostics

#ifndef HEADER
#define HEADER

template<typename T> constexpr T f();
constexpr int g() { return f<int>(); } // #1

#else /*included pch*/

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

#endif // HEADER
30 changes: 30 additions & 0 deletions clang/test/SemaTemplate/instantiate-used-constexpr-function.cpp
Original file line number Diff line number Diff line change
@@ -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> str{};

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();

}

0 comments on commit 030047c

Please sign in to comment.