[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:
Saar Raz 2020-01-22 02:50:12 +02:00
parent a156da5fb3
commit b933d37cd3
10 changed files with 144 additions and 55 deletions

View File

@ -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

View File

@ -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")

View File

@ -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 {

View File

@ -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

View File

@ -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"

View File

@ -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);
}

View File

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

View File

@ -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

View File

@ -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,

View File

@ -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