forked from OSchip/llvm-project
[Concepts] Constraint Satisfaction Caching
Add a simple cache for constraint satisfaction results. Whether or not this simple caching would be permitted in final C++2a is currently being discussed but it is required for acceptable performance so we use it in the meantime, with the possibility of adding some cache invalidation mechanisms later. Differential Revision: https://reviews.llvm.org/D72552
This commit is contained in:
parent
a156da5fb3
commit
b933d37cd3
|
@ -24,9 +24,23 @@ namespace clang {
|
|||
class ConceptDecl;
|
||||
class ConceptSpecializationExpr;
|
||||
|
||||
/// \brief The result of a constraint satisfaction check, containing the
|
||||
/// necessary information to diagnose an unsatisfied constraint.
|
||||
struct ConstraintSatisfaction {
|
||||
/// The result of a constraint satisfaction check, containing the necessary
|
||||
/// information to diagnose an unsatisfied constraint.
|
||||
class ConstraintSatisfaction : public llvm::FoldingSetNode {
|
||||
// The template-like entity that 'owns' the constraint checked here (can be a
|
||||
// constrained entity or a concept).
|
||||
NamedDecl *ConstraintOwner = nullptr;
|
||||
llvm::SmallVector<TemplateArgument, 4> TemplateArgs;
|
||||
|
||||
public:
|
||||
|
||||
ConstraintSatisfaction() = default;
|
||||
|
||||
ConstraintSatisfaction(NamedDecl *ConstraintOwner,
|
||||
ArrayRef<TemplateArgument> TemplateArgs) :
|
||||
ConstraintOwner(ConstraintOwner), TemplateArgs(TemplateArgs.begin(),
|
||||
TemplateArgs.end()) { }
|
||||
|
||||
using SubstitutionDiagnostic = std::pair<SourceLocation, StringRef>;
|
||||
using Detail = llvm::PointerUnion<Expr *, SubstitutionDiagnostic *>;
|
||||
|
||||
|
@ -38,9 +52,13 @@ struct ConstraintSatisfaction {
|
|||
/// invalid expression.
|
||||
llvm::SmallVector<std::pair<const Expr *, Detail>, 4> Details;
|
||||
|
||||
// This can leak if used in an AST node, use ASTConstraintSatisfaction
|
||||
// instead.
|
||||
void *operator new(size_t bytes, ASTContext &C) = delete;
|
||||
void Profile(llvm::FoldingSetNodeID &ID, const ASTContext &C) {
|
||||
Profile(ID, C, ConstraintOwner, TemplateArgs);
|
||||
}
|
||||
|
||||
static void Profile(llvm::FoldingSetNodeID &ID, const ASTContext &C,
|
||||
NamedDecl *ConstraintOwner,
|
||||
ArrayRef<TemplateArgument> TemplateArgs);
|
||||
};
|
||||
|
||||
/// Pairs of unsatisfied atomic constraint expressions along with the
|
||||
|
|
|
@ -238,6 +238,7 @@ LANGOPT(AlignedAllocation , 1, 0, "aligned allocation")
|
|||
LANGOPT(AlignedAllocationUnavailable, 1, 0, "aligned allocation functions are unavailable")
|
||||
LANGOPT(NewAlignOverride , 32, 0, "maximum alignment guaranteed by '::operator new(size_t)'")
|
||||
LANGOPT(ConceptsTS , 1, 0, "enable C++ Extensions for Concepts")
|
||||
LANGOPT(ConceptSatisfactionCaching , 1, 1, "enable satisfaction caching for C++2a Concepts")
|
||||
BENIGN_LANGOPT(ModulesCodegen , 1, 0, "Modules code generation")
|
||||
BENIGN_LANGOPT(ModulesDebugInfo , 1, 0, "Modules debug info")
|
||||
BENIGN_LANGOPT(ElideConstructors , 1, 1, "C++ copy constructor elision")
|
||||
|
|
|
@ -557,6 +557,9 @@ def ftest_module_file_extension_EQ :
|
|||
"The argument is parsed as blockname:major:minor:hashed:user info">;
|
||||
def fconcepts_ts : Flag<["-"], "fconcepts-ts">,
|
||||
HelpText<"Enable C++ Extensions for Concepts.">;
|
||||
def fno_concept_satisfaction_caching : Flag<["-"],
|
||||
"fno-concept-satisfaction-caching">,
|
||||
HelpText<"Disable satisfaction caching for C++2a Concepts.">;
|
||||
|
||||
let Group = Action_Group in {
|
||||
|
||||
|
|
|
@ -6232,6 +6232,9 @@ private:
|
|||
llvm::DenseMap<NamedDecl *, NormalizedConstraint *>
|
||||
NormalizationCache;
|
||||
|
||||
llvm::ContextualFoldingSet<ConstraintSatisfaction, const ASTContext &>
|
||||
SatisfactionCache;
|
||||
|
||||
public:
|
||||
const NormalizedConstraint *
|
||||
getNormalizedAssociatedConstraints(
|
||||
|
@ -6258,6 +6261,8 @@ public:
|
|||
|
||||
/// \brief Check whether the given list of constraint expressions are
|
||||
/// satisfied (as if in a 'conjunction') given template arguments.
|
||||
/// \param Template the template-like entity that triggered the constraints
|
||||
/// check (either a concept or a constrained entity).
|
||||
/// \param ConstraintExprs a list of constraint expressions, treated as if
|
||||
/// they were 'AND'ed together.
|
||||
/// \param TemplateArgs the list of template arguments to substitute into the
|
||||
|
@ -6269,23 +6274,10 @@ public:
|
|||
/// expression.
|
||||
/// \returns true if an error occurred and satisfaction could not be checked,
|
||||
/// false otherwise.
|
||||
bool CheckConstraintSatisfaction(TemplateDecl *Template,
|
||||
ArrayRef<const Expr *> ConstraintExprs,
|
||||
ArrayRef<TemplateArgument> TemplateArgs,
|
||||
SourceRange TemplateIDRange,
|
||||
ConstraintSatisfaction &Satisfaction);
|
||||
|
||||
bool CheckConstraintSatisfaction(ClassTemplatePartialSpecializationDecl *TD,
|
||||
ArrayRef<const Expr *> ConstraintExprs,
|
||||
ArrayRef<TemplateArgument> TemplateArgs,
|
||||
SourceRange TemplateIDRange,
|
||||
ConstraintSatisfaction &Satisfaction);
|
||||
|
||||
bool CheckConstraintSatisfaction(VarTemplatePartialSpecializationDecl *TD,
|
||||
ArrayRef<const Expr *> ConstraintExprs,
|
||||
ArrayRef<TemplateArgument> TemplateArgs,
|
||||
SourceRange TemplateIDRange,
|
||||
ConstraintSatisfaction &Satisfaction);
|
||||
bool CheckConstraintSatisfaction(
|
||||
NamedDecl *Template, ArrayRef<const Expr *> ConstraintExprs,
|
||||
ArrayRef<TemplateArgument> TemplateArgs,
|
||||
SourceRange TemplateIDRange, ConstraintSatisfaction &Satisfaction);
|
||||
|
||||
/// \brief Check whether the given non-dependent constraint expression is
|
||||
/// satisfied. Returns false and updates Satisfaction with the satisfaction
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#define LLVM_CLANG_SEMA_TEMPLATEDEDUCTION_H
|
||||
|
||||
#include "clang/Sema/Ownership.h"
|
||||
#include "clang/Sema/SemaConcept.h"
|
||||
#include "clang/AST/ASTConcept.h"
|
||||
#include "clang/AST/DeclAccessPair.h"
|
||||
#include "clang/AST/DeclTemplate.h"
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include "clang/AST/ASTConcept.h"
|
||||
#include "clang/AST/ASTContext.h"
|
||||
#include "clang/Sema/SemaConcept.h"
|
||||
using namespace clang;
|
||||
|
||||
ASTConstraintSatisfaction::ASTConstraintSatisfaction(const ASTContext &C,
|
||||
|
@ -53,3 +54,12 @@ ASTConstraintSatisfaction::Create(const ASTContext &C,
|
|||
void *Mem = C.Allocate(size, alignof(ASTConstraintSatisfaction));
|
||||
return new (Mem) ASTConstraintSatisfaction(C, Satisfaction);
|
||||
}
|
||||
|
||||
void ConstraintSatisfaction::Profile(
|
||||
llvm::FoldingSetNodeID &ID, const ASTContext &C, NamedDecl *ConstraintOwner,
|
||||
ArrayRef<TemplateArgument> TemplateArgs) {
|
||||
ID.AddPointer(ConstraintOwner);
|
||||
ID.AddInteger(TemplateArgs.size());
|
||||
for (auto &Arg : TemplateArgs)
|
||||
Arg.Profile(ID, C);
|
||||
}
|
||||
|
|
|
@ -2859,6 +2859,8 @@ static void ParseLangArgs(LangOptions &Opts, ArgList &Args, InputKind IK,
|
|||
Opts.NewAlignOverride = 0;
|
||||
}
|
||||
Opts.ConceptsTS = Args.hasArg(OPT_fconcepts_ts);
|
||||
Opts.ConceptSatisfactionCaching =
|
||||
!Args.hasArg(OPT_fno_concept_satisfaction_caching);
|
||||
Opts.HeinousExtensions = Args.hasArg(OPT_fheinous_gnu_extensions);
|
||||
Opts.AccessControl = !Args.hasArg(OPT_fno_access_control);
|
||||
Opts.ElideConstructors = !Args.hasArg(OPT_fno_elide_constructors);
|
||||
|
|
|
@ -168,10 +168,10 @@ Sema::Sema(Preprocessor &pp, ASTContext &ctxt, ASTConsumer &consumer,
|
|||
TUKind(TUKind), NumSFINAEErrors(0),
|
||||
FullyCheckedComparisonCategories(
|
||||
static_cast<unsigned>(ComparisonCategoryType::Last) + 1),
|
||||
AccessCheckingSFINAE(false), InNonInstantiationSFINAEContext(false),
|
||||
NonInstantiationEntries(0), ArgumentPackSubstitutionIndex(-1),
|
||||
CurrentInstantiationScope(nullptr), DisableTypoCorrection(false),
|
||||
TyposCorrected(0), AnalysisWarnings(*this),
|
||||
SatisfactionCache(Context), AccessCheckingSFINAE(false),
|
||||
InNonInstantiationSFINAEContext(false), NonInstantiationEntries(0),
|
||||
ArgumentPackSubstitutionIndex(-1), CurrentInstantiationScope(nullptr),
|
||||
DisableTypoCorrection(false), TyposCorrected(0), AnalysisWarnings(*this),
|
||||
ThreadSafetyDeclCache(nullptr), VarDataSharingAttributesStack(nullptr),
|
||||
CurScope(nullptr), Ident_super(nullptr), Ident___float128(nullptr) {
|
||||
TUScope = nullptr;
|
||||
|
@ -394,6 +394,14 @@ Sema::~Sema() {
|
|||
if (isMultiplexExternalSource)
|
||||
delete ExternalSource;
|
||||
|
||||
// Delete cached satisfactions.
|
||||
std::vector<ConstraintSatisfaction *> Satisfactions;
|
||||
Satisfactions.reserve(Satisfactions.size());
|
||||
for (auto &Node : SatisfactionCache)
|
||||
Satisfactions.push_back(&Node);
|
||||
for (auto *Node : Satisfactions)
|
||||
delete Node;
|
||||
|
||||
threadSafety::threadSafetyCleanup(ThreadSafetyDeclCache);
|
||||
|
||||
// Destroys data sharing attributes stack for OpenMP
|
||||
|
|
|
@ -272,36 +272,56 @@ static bool CheckConstraintSatisfaction(Sema &S, TemplateDeclT *Template,
|
|||
return false;
|
||||
}
|
||||
|
||||
bool Sema::CheckConstraintSatisfaction(TemplateDecl *Template,
|
||||
ArrayRef<const Expr *> ConstraintExprs,
|
||||
ArrayRef<TemplateArgument> TemplateArgs,
|
||||
SourceRange TemplateIDRange,
|
||||
ConstraintSatisfaction &Satisfaction) {
|
||||
return ::CheckConstraintSatisfaction(*this, Template, ConstraintExprs,
|
||||
TemplateArgs, TemplateIDRange,
|
||||
Satisfaction);
|
||||
}
|
||||
bool Sema::CheckConstraintSatisfaction(
|
||||
NamedDecl *Template, ArrayRef<const Expr *> ConstraintExprs,
|
||||
ArrayRef<TemplateArgument> TemplateArgs, SourceRange TemplateIDRange,
|
||||
ConstraintSatisfaction &OutSatisfaction) {
|
||||
if (ConstraintExprs.empty()) {
|
||||
OutSatisfaction.IsSatisfied = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
Sema::CheckConstraintSatisfaction(ClassTemplatePartialSpecializationDecl* Part,
|
||||
ArrayRef<const Expr *> ConstraintExprs,
|
||||
ArrayRef<TemplateArgument> TemplateArgs,
|
||||
SourceRange TemplateIDRange,
|
||||
ConstraintSatisfaction &Satisfaction) {
|
||||
return ::CheckConstraintSatisfaction(*this, Part, ConstraintExprs,
|
||||
TemplateArgs, TemplateIDRange,
|
||||
Satisfaction);
|
||||
}
|
||||
llvm::FoldingSetNodeID ID;
|
||||
void *InsertPos;
|
||||
ConstraintSatisfaction *Satisfaction = nullptr;
|
||||
if (LangOpts.ConceptSatisfactionCaching) {
|
||||
ConstraintSatisfaction::Profile(ID, Context, Template, TemplateArgs);
|
||||
Satisfaction = SatisfactionCache.FindNodeOrInsertPos(ID, InsertPos);
|
||||
if (Satisfaction) {
|
||||
OutSatisfaction = *Satisfaction;
|
||||
return false;
|
||||
}
|
||||
Satisfaction = new ConstraintSatisfaction(Template, TemplateArgs);
|
||||
} else {
|
||||
Satisfaction = &OutSatisfaction;
|
||||
}
|
||||
bool Failed;
|
||||
if (auto *T = dyn_cast<TemplateDecl>(Template))
|
||||
Failed = ::CheckConstraintSatisfaction(*this, T, ConstraintExprs,
|
||||
TemplateArgs, TemplateIDRange,
|
||||
*Satisfaction);
|
||||
else if (auto *P =
|
||||
dyn_cast<ClassTemplatePartialSpecializationDecl>(Template))
|
||||
Failed = ::CheckConstraintSatisfaction(*this, P, ConstraintExprs,
|
||||
TemplateArgs, TemplateIDRange,
|
||||
*Satisfaction);
|
||||
else
|
||||
Failed = ::CheckConstraintSatisfaction(
|
||||
*this, cast<VarTemplatePartialSpecializationDecl>(Template),
|
||||
ConstraintExprs, TemplateArgs, TemplateIDRange, *Satisfaction);
|
||||
if (Failed) {
|
||||
if (LangOpts.ConceptSatisfactionCaching)
|
||||
delete Satisfaction;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
Sema::CheckConstraintSatisfaction(VarTemplatePartialSpecializationDecl* Partial,
|
||||
ArrayRef<const Expr *> ConstraintExprs,
|
||||
ArrayRef<TemplateArgument> TemplateArgs,
|
||||
SourceRange TemplateIDRange,
|
||||
ConstraintSatisfaction &Satisfaction) {
|
||||
return ::CheckConstraintSatisfaction(*this, Partial, ConstraintExprs,
|
||||
TemplateArgs, TemplateIDRange,
|
||||
Satisfaction);
|
||||
if (LangOpts.ConceptSatisfactionCaching) {
|
||||
// We cannot use InsertNode here because CheckConstraintSatisfaction might
|
||||
// have invalidated it.
|
||||
SatisfactionCache.InsertNode(Satisfaction);
|
||||
OutSatisfaction = *Satisfaction;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Sema::CheckConstraintSatisfaction(const Expr *ConstraintExpr,
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
// RUN: %clang_cc1 -std=c++2a -fconcepts-ts -verify %s
|
||||
// RUN: %clang_cc1 -std=c++2a -fconcepts-ts -verify %s -fno-concept-satisfaction-caching -DNO_CACHE
|
||||
// expected-no-diagnostics
|
||||
|
||||
template<typename T>
|
||||
concept C = (f(T()), true);
|
||||
|
||||
template<typename T>
|
||||
constexpr bool foo() { return false; }
|
||||
|
||||
template<typename T>
|
||||
requires (f(T()), true)
|
||||
constexpr bool foo() requires (f(T()), true) { return true; }
|
||||
|
||||
namespace a {
|
||||
struct A {};
|
||||
void f(A a);
|
||||
}
|
||||
|
||||
static_assert(C<a::A>);
|
||||
static_assert(foo<a::A>());
|
||||
|
||||
namespace a {
|
||||
// This makes calls to f ambiguous, but the second check will still succeed
|
||||
// because the constraint satisfaction results are cached.
|
||||
void f(A a, int = 2);
|
||||
}
|
||||
#ifdef NO_CACHE
|
||||
static_assert(!C<a::A>);
|
||||
static_assert(!foo<a::A>());
|
||||
#else
|
||||
static_assert(C<a::A>);
|
||||
static_assert(foo<a::A>());
|
||||
#endif
|
Loading…
Reference in New Issue