[clang] [Clang][NFC] Improve ConstraintSatisfaction (PR #230369)
Younan Zhang via cfe-commits
cfe-commits at lists.llvm.org
Fri Oct 9 04:10:01 PDT 2026
https://github.com/zyn0217 updated https://github.com/llvm/llvm-project/pull/230369
>From 4c24d35b9592a815d31ed590deadaa36d0414396 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Fri, 9 Oct 2026 12:32:41 +0800
Subject: [PATCH 1/2] [Clang] Improve ConstraintSatisfaction
---
clang/include/clang/AST/ASTConcept.h | 49 +++++++++----------
clang/include/clang/AST/ExprConcepts.h | 4 +-
clang/include/clang/Sema/Sema.h | 7 +--
clang/lib/AST/ASTConcept.cpp | 12 ++---
clang/lib/AST/ASTImporter.cpp | 4 +-
clang/lib/AST/ComputeDependence.cpp | 2 +-
clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp | 2 +-
clang/lib/Sema/Sema.cpp | 8 +--
clang/lib/Sema/SemaConcept.cpp | 12 ++---
clang/lib/Sema/SemaDeclCXX.cpp | 2 +-
clang/lib/Sema/SemaExprCXX.cpp | 10 +---
clang/lib/Sema/TreeTransform.h | 4 +-
clang/lib/Serialization/ASTWriterStmt.cpp | 8 +--
13 files changed, 52 insertions(+), 72 deletions(-)
diff --git a/clang/include/clang/AST/ASTConcept.h b/clang/include/clang/AST/ASTConcept.h
index 62fe2c0f653d57..71a16931d6b3d0 100644
--- a/clang/include/clang/AST/ASTConcept.h
+++ b/clang/include/clang/AST/ASTConcept.h
@@ -44,31 +44,15 @@ using UnsatisfiedConstraintRecord =
/// 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).
- const NamedDecl *ConstraintOwner = nullptr;
- llvm::SmallVector<TemplateArgument, 4> TemplateArgs;
-
+class ConstraintSatisfaction {
public:
-
- ConstraintSatisfaction() = default;
-
- ConstraintSatisfaction(const NamedDecl *ConstraintOwner,
- ArrayRef<TemplateArgument> TemplateArgs)
- : ConstraintOwner(ConstraintOwner), TemplateArgs(TemplateArgs) {}
-
bool IsSatisfied = false;
bool ContainsErrors = false;
/// \brief The substituted constraint expr, if the template arguments could be
/// substituted into them, or a diagnostic if substitution resulted in an
/// invalid expression.
- llvm::SmallVector<UnsatisfiedConstraintRecord, 4> Details;
-
- void Profile(llvm::FoldingSetNodeID &ID, const ASTContext &C) {
- Profile(ID, C, ConstraintOwner, TemplateArgs);
- }
+ llvm::SmallVector<UnsatisfiedConstraintRecord, 1> Details;
static void Profile(llvm::FoldingSetNodeID &ID, const ASTContext &C,
const NamedDecl *ConstraintOwner,
@@ -86,13 +70,24 @@ class ConstraintSatisfaction : public llvm::FoldingSetNode {
/// necessary information to diagnose an unsatisfied constraint.
///
/// This is safe to store in an AST node, as opposed to ConstraintSatisfaction.
-struct ASTConstraintSatisfaction final :
- llvm::TrailingObjects<ASTConstraintSatisfaction,
- UnsatisfiedConstraintRecord> {
- std::size_t NumRecords;
+class ASTConstraintSatisfaction final
+ : llvm::TrailingObjects<ASTConstraintSatisfaction,
+ UnsatisfiedConstraintRecord> {
+ friend TrailingObjects;
+ friend class ASTStmtWriter;
+ friend class ASTStmtReader;
+
+ unsigned NumRecords : 30;
bool IsSatisfied : 1;
bool ContainsErrors : 1;
+ ASTConstraintSatisfaction(const ASTContext &C,
+ const ConstraintSatisfaction &Satisfaction);
+
+ ASTConstraintSatisfaction(const ASTContext &C,
+ const ASTConstraintSatisfaction &Satisfaction);
+
+public:
const UnsatisfiedConstraintRecord *begin() const {
return getTrailingObjects();
}
@@ -105,13 +100,15 @@ struct ASTConstraintSatisfaction final :
return {begin(), end()};
}
- ASTConstraintSatisfaction(const ASTContext &C,
- const ConstraintSatisfaction &Satisfaction);
- ASTConstraintSatisfaction(const ASTContext &C,
- const ASTConstraintSatisfaction &Satisfaction);
+ bool isSatisfied() const { return IsSatisfied; }
+
+ bool containsErrors() const { return ContainsErrors; }
+
+ unsigned record_size() const { return NumRecords; }
static ASTConstraintSatisfaction *
Create(const ASTContext &C, const ConstraintSatisfaction &Satisfaction);
+
static ASTConstraintSatisfaction *
Rebuild(const ASTContext &C, const ASTConstraintSatisfaction &Satisfaction);
};
diff --git a/clang/include/clang/AST/ExprConcepts.h b/clang/include/clang/AST/ExprConcepts.h
index 18ce5a94ebef3c..7d977069e80964 100644
--- a/clang/include/clang/AST/ExprConcepts.h
+++ b/clang/include/clang/AST/ExprConcepts.h
@@ -128,7 +128,7 @@ class ConceptSpecializationExpr final : public Expr {
bool isSatisfied() const {
assert(!isValueDependent() &&
"isSatisfied called on a dependent ConceptSpecializationExpr");
- return Satisfaction->IsSatisfied;
+ return Satisfaction->isSatisfied();
}
/// \brief Get elaborated satisfaction info about the template arguments'
@@ -463,7 +463,7 @@ class NestedRequirement : public Requirement {
: Requirement(RK_Nested,
/*IsDependent=*/false,
/*ContainsUnexpandedParameterPack*/ false,
- Satisfaction->IsSatisfied),
+ Satisfaction->isSatisfied()),
Satisfaction(Satisfaction), HasInvalidConstraint(true),
InvalidConstraintEntity(InvalidConstraintEntity) {}
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 0864337a9374c6..e33d15c11cd757 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -115,7 +115,7 @@ struct InlineAsmIdentifierInfo;
namespace clang {
class ADLResult;
class APValue;
-struct ASTConstraintSatisfaction;
+class ASTConstraintSatisfaction;
class ASTConsumer;
class ASTContext;
class ASTDeclReader;
@@ -8883,9 +8883,6 @@ class Sema final : public SemaBase {
concepts::TypeRequirement *BuildTypeRequirement(
concepts::Requirement::SubstitutionDiagnostic *SubstDiag);
concepts::NestedRequirement *BuildNestedRequirement(Expr *E);
- concepts::NestedRequirement *
- BuildNestedRequirement(StringRef InvalidConstraintEntity,
- const ASTConstraintSatisfaction &Satisfaction);
ExprResult ActOnRequiresExpr(SourceLocation RequiresKWLoc,
RequiresExprBodyDecl *Body,
SourceLocation LParenLoc,
@@ -15165,7 +15162,7 @@ class Sema final : public SemaBase {
/// Cache whether the associated constraint of a declaration
/// is satisfied.
- llvm::ContextualFoldingSet<ConstraintSatisfaction, const ASTContext &>
+ llvm::DenseMap<llvm::FoldingSetNodeID, ConstraintSatisfaction *>
SatisfactionCache;
// The current stack of constraint satisfactions, so we can exit-early.
diff --git a/clang/lib/AST/ASTConcept.cpp b/clang/lib/AST/ASTConcept.cpp
index 2b04821a157da4..ee8ef848cebe09 100644
--- a/clang/lib/AST/ASTConcept.cpp
+++ b/clang/lib/AST/ASTConcept.cpp
@@ -43,9 +43,9 @@ CreateUnsatisfiedConstraintRecord(const ASTContext &C,
ASTConstraintSatisfaction::ASTConstraintSatisfaction(
const ASTContext &C, const ConstraintSatisfaction &Satisfaction)
- : NumRecords{Satisfaction.Details.size()},
- IsSatisfied{Satisfaction.IsSatisfied}, ContainsErrors{
- Satisfaction.ContainsErrors} {
+ : NumRecords(Satisfaction.Details.size()),
+ IsSatisfied(Satisfaction.IsSatisfied),
+ ContainsErrors(Satisfaction.ContainsErrors) {
for (unsigned I = 0; I < NumRecords; ++I)
CreateUnsatisfiedConstraintRecord(C, Satisfaction.Details[I],
getTrailingObjects() + I);
@@ -53,9 +53,9 @@ ASTConstraintSatisfaction::ASTConstraintSatisfaction(
ASTConstraintSatisfaction::ASTConstraintSatisfaction(
const ASTContext &C, const ASTConstraintSatisfaction &Satisfaction)
- : NumRecords{Satisfaction.NumRecords},
- IsSatisfied{Satisfaction.IsSatisfied},
- ContainsErrors{Satisfaction.ContainsErrors} {
+ : NumRecords(Satisfaction.NumRecords),
+ IsSatisfied(Satisfaction.IsSatisfied),
+ ContainsErrors(Satisfaction.ContainsErrors) {
for (unsigned I = 0; I < NumRecords; ++I)
CreateUnsatisfiedConstraintRecord(C, *(Satisfaction.begin() + I),
getTrailingObjects() + I);
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index 22c3af47f451f6..10ee9b75afec0f 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -1075,8 +1075,8 @@ StringRef ASTNodeImporter::ImportASTStringRef(StringRef FromStr) {
Error ASTNodeImporter::ImportConstraintSatisfaction(
const ASTConstraintSatisfaction &FromSat, ConstraintSatisfaction &ToSat) {
- ToSat.IsSatisfied = FromSat.IsSatisfied;
- ToSat.ContainsErrors = FromSat.ContainsErrors;
+ ToSat.IsSatisfied = FromSat.isSatisfied();
+ ToSat.ContainsErrors = FromSat.containsErrors();
if (!ToSat.IsSatisfied) {
for (auto Record = FromSat.begin(); Record != FromSat.end(); ++Record) {
if (const Expr *E = Record->dyn_cast<const Expr *>()) {
diff --git a/clang/lib/AST/ComputeDependence.cpp b/clang/lib/AST/ComputeDependence.cpp
index 7ea1c67ff77389..c4bf3681a79122 100644
--- a/clang/lib/AST/ComputeDependence.cpp
+++ b/clang/lib/AST/ComputeDependence.cpp
@@ -955,7 +955,7 @@ ExprDependence clang::computeDependence(ConceptSpecializationExpr *E,
ExprDependence D =
ValueDependent ? ExprDependence::Value : ExprDependence::None;
auto Res = D | toExprDependence(TA);
- if(!ValueDependent && E->getSatisfaction().ContainsErrors)
+ if (!ValueDependent && E->getSatisfaction().containsErrors())
Res |= ExprDependence::Error;
return Res;
}
diff --git a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
index e49b1cba7b1dde..54d5e3eba66af8 100644
--- a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
+++ b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
@@ -433,7 +433,7 @@ TemplateParameterListBuilder::constructConceptSpecializationExpr(
// Constraint satisfaction is used to construct the
// ConceptSpecailizationExpr, and represents the 2nd Template Argument,
// located at the bottom of the sample AST above.
- const ConstraintSatisfaction CS(CD, {ConceptTA});
+ ConstraintSatisfaction CS;
TemplateArgumentLoc TAL =
S.getTrivialTemplateArgumentLoc(ConceptTA, QualType(), SourceLocation());
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 21f71d7f8b40e2..25ead1308c537a 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -328,7 +328,7 @@ Sema::Sema(Preprocessor &pp, ASTContext &ctxt, ASTConsumer &consumer,
GlobalNewDeleteDeclared(false), DisableTypoCorrection(false),
TyposCorrected(0), IsBuildingRecoveryCallExpr(false),
CurrentInstantiationScope(nullptr), NonInstantiationEntries(0),
- ArgPackSubstIndex(std::nullopt), SatisfactionCache(Context) {
+ ArgPackSubstIndex(std::nullopt) {
assert(pp.TUKind == TUKind);
TUScope = nullptr;
@@ -626,11 +626,7 @@ Sema::~Sema() {
ExternalSema->ForgetSema();
// Delete cached satisfactions.
- std::vector<ConstraintSatisfaction *> Satisfactions;
- Satisfactions.reserve(SatisfactionCache.size());
- for (auto &Node : SatisfactionCache)
- Satisfactions.push_back(&Node);
- for (auto *Node : Satisfactions)
+ for (auto [_, Node] : SatisfactionCache)
delete Node;
threadSafety::threadSafetyCleanup(ThreadSafetyDeclCache);
diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp
index fb66bdd6d2bd04..15b6b9c0c8b396 100644
--- a/clang/lib/Sema/SemaConcept.cpp
+++ b/clang/lib/Sema/SemaConcept.cpp
@@ -1323,14 +1323,12 @@ bool Sema::CheckConstraintSatisfaction(
llvm::FoldingSetNodeID ID;
ConstraintSatisfaction::Profile(ID, Context, Owner, FlattenedArgs);
- llvm::FoldingSetInsertToken Token;
- if (auto *Cached = SatisfactionCache.lookup(ID, Token)) {
+ if (auto *Cached = SatisfactionCache.lookup(ID)) {
OutSatisfaction = *Cached;
return false;
}
- auto Satisfaction =
- std::make_unique<ConstraintSatisfaction>(Owner, FlattenedArgs);
+ auto Satisfaction = std::make_unique<ConstraintSatisfaction>();
if (::CheckConstraintSatisfaction(
*this, Template, AssociatedConstraints, TemplateArgsLists,
TemplateIDRange, *Satisfaction, ConvertedExpr, TopLevelConceptId)) {
@@ -1338,7 +1336,7 @@ bool Sema::CheckConstraintSatisfaction(
return true;
}
- if (auto *Cached = SatisfactionCache.lookup(ID, Token)) {
+ if (auto *Cached = SatisfactionCache.lookup(ID)) {
// The evaluation of this constraint resulted in us trying to re-evaluate it
// recursively. This isn't really possible, except we try to form a
// RecoveryExpr as a part of the evaluation. If this is the case, just
@@ -1354,7 +1352,7 @@ bool Sema::CheckConstraintSatisfaction(
// Else we can simply add this satisfaction to the list.
OutSatisfaction = *Satisfaction;
// Note that entries of SatisfactionCache are deleted in Sema's destructor.
- SatisfactionCache.insert(Satisfaction.release());
+ SatisfactionCache.insert({ID, Satisfaction.release()});
return false;
}
@@ -2060,7 +2058,7 @@ void Sema::DiagnoseUnsatisfiedConstraint(
const ASTConstraintSatisfaction &Satisfaction =
ConstraintExpr->getSatisfaction();
- assert(!Satisfaction.IsSatisfied &&
+ assert(!Satisfaction.isSatisfied() &&
"Attempted to diagnose a satisfied constraint");
::DiagnoseUnsatisfiedConstraint(*this, Satisfaction.records(),
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index ea628f29d8a005..af8e53d4aea2ed 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18092,7 +18092,7 @@ Decl *Sema::BuildStaticAssertDeclaration(SourceLocation StaticAssertLoc,
dyn_cast_or_null<ConceptSpecializationExpr>(InnerCond)) {
const ASTConstraintSatisfaction &Satisfaction =
ConceptIDExpr->getSatisfaction();
- if (!Satisfaction.ContainsErrors || Satisfaction.NumRecords) {
+ if (!Satisfaction.containsErrors() || Satisfaction.record_size()) {
Diag(AssertExpr->getBeginLoc(), diag::err_static_assert_failed)
<< !HasMessage << Msg.str() << AssertExpr->getSourceRange();
// Drill down into concept specialization expressions to see why they
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index deb7bdd09fdc7d..e65b8240255c96 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -8121,7 +8121,7 @@ Sema::BuildExprRequirement(
if (!HasError) {
SubstitutedConstraintExpr =
cast<ConceptSpecializationExpr>(Constraint.get());
- if (SubstitutedConstraintExpr->getSatisfaction().ContainsErrors)
+ if (SubstitutedConstraintExpr->getSatisfaction().containsErrors())
HasError = true;
}
if (HasError) {
@@ -8191,14 +8191,6 @@ Sema::BuildNestedRequirement(Expr *Constraint) {
Satisfaction);
}
-concepts::NestedRequirement *
-Sema::BuildNestedRequirement(StringRef InvalidConstraintEntity,
- const ASTConstraintSatisfaction &Satisfaction) {
- return new (Context) concepts::NestedRequirement(
- InvalidConstraintEntity,
- ASTConstraintSatisfaction::Rebuild(Context, Satisfaction));
-}
-
RequiresExprBodyDecl *
Sema::ActOnStartRequiresExpr(SourceLocation RequiresKWLoc,
ArrayRef<ParmVarDecl *> LocalParameters,
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index c8458fda58a88e..841228d9272050 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -3872,8 +3872,8 @@ class TreeTransform {
concepts::NestedRequirement *
RebuildNestedRequirement(StringRef InvalidConstraintEntity,
const ASTConstraintSatisfaction &Satisfaction) {
- return SemaRef.BuildNestedRequirement(InvalidConstraintEntity,
- Satisfaction);
+ return new (getSema().Context)
+ concepts::NestedRequirement(InvalidConstraintEntity, &Satisfaction);
}
concepts::NestedRequirement *RebuildNestedRequirement(Expr *Constraint) {
diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp
index 513c832c38ffd9..dea4d34fb73f80 100644
--- a/clang/lib/Serialization/ASTWriterStmt.cpp
+++ b/clang/lib/Serialization/ASTWriterStmt.cpp
@@ -490,10 +490,10 @@ void ASTStmtWriter::VisitDependentCoawaitExpr(DependentCoawaitExpr *E) {
static void
addConstraintSatisfaction(ASTRecordWriter &Record,
const ASTConstraintSatisfaction &Satisfaction) {
- Record.push_back(Satisfaction.IsSatisfied);
- Record.push_back(Satisfaction.ContainsErrors);
- if (!Satisfaction.IsSatisfied) {
- Record.push_back(Satisfaction.NumRecords);
+ Record.push_back(Satisfaction.isSatisfied());
+ Record.push_back(Satisfaction.containsErrors());
+ if (!Satisfaction.isSatisfied()) {
+ Record.push_back(Satisfaction.record_size());
for (const auto &DetailRecord : Satisfaction) {
if (auto *Diag = dyn_cast<const ConstraintSubstitutionDiagnostic *>(
DetailRecord)) {
>From 2e21c48624922a0d57d1f56e6f16119f4a7150a9 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Fri, 9 Oct 2026 19:09:26 +0800
Subject: [PATCH 2/2] Remove duplicate cache lookup
---
clang/lib/Sema/SemaConcept.cpp | 25 ++++++++-----------------
1 file changed, 8 insertions(+), 17 deletions(-)
diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp
index 15b6b9c0c8b396..b02faa5f8ff957 100644
--- a/clang/lib/Sema/SemaConcept.cpp
+++ b/clang/lib/Sema/SemaConcept.cpp
@@ -597,9 +597,11 @@ class ConstraintSatisfactionChecker {
auto &Cached = Iter->second.Satisfaction;
Satisfaction.ContainsErrors = Cached.ContainsErrors;
Satisfaction.IsSatisfied = Cached.IsSatisfied;
- Satisfaction.Details.insert(Satisfaction.Details.begin() +
- PreviousDetailsSize,
- Cached.Details.begin(), Cached.Details.end());
+ SmallVector<UnsatisfiedConstraintRecord, 1> Details(
+ Satisfaction.Details.begin(),
+ Satisfaction.Details.begin() + PreviousDetailsSize);
+ Details.append(Cached.Details.begin(), Cached.Details.end());
+ std::swap(Details, Satisfaction.Details);
return &Iter->second;
}
@@ -1336,23 +1338,12 @@ bool Sema::CheckConstraintSatisfaction(
return true;
}
- if (auto *Cached = SatisfactionCache.lookup(ID)) {
- // The evaluation of this constraint resulted in us trying to re-evaluate it
- // recursively. This isn't really possible, except we try to form a
- // RecoveryExpr as a part of the evaluation. If this is the case, just
- // return the 'cached' version (which will have the same result), and save
- // ourselves the extra-insert. If it ever becomes possible to legitimately
- // recursively check a constraint, we should skip checking the 'inner' one
- // above, and replace the cached version with this one, as it would be more
- // specific.
- OutSatisfaction = *Cached;
- return false;
- }
-
// Else we can simply add this satisfaction to the list.
OutSatisfaction = *Satisfaction;
// Note that entries of SatisfactionCache are deleted in Sema's destructor.
- SatisfactionCache.insert({ID, Satisfaction.release()});
+ auto [Iter, Inserted] = SatisfactionCache.insert({ID, nullptr});
+ if (Inserted)
+ Iter->second = Satisfaction.release();
return false;
}
More information about the cfe-commits
mailing list