[Clang] Avoid building unnecessary expressions when checking satisfaction (#164611)

When establishing constraint satisfaction, we were building expressions
even for compound constraint,

This is unnecessary extra work that accounts for ~20% of the performance
regression observed here
https://github.com/llvm/llvm-project/pull/161671#issuecomment-3420976661
This commit is contained in:
Corentin Jabot 2025-10-22 18:11:56 +02:00 committed by GitHub
parent 57a8599d14
commit 72616c5ca9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 34 additions and 43 deletions

View File

@ -86,7 +86,7 @@ void ConstraintSatisfaction::Profile(llvm::FoldingSetNodeID &ID,
ID.AddPointer(ConstraintOwner);
ID.AddInteger(TemplateArgs.size());
for (auto &Arg : TemplateArgs)
C.getCanonicalTemplateArgument(Arg).Profile(ID, C);
Arg.Profile(ID, C);
}
ConceptReference *

View File

@ -417,8 +417,8 @@ class ConstraintSatisfactionChecker {
const NamedDecl *Template;
SourceLocation TemplateNameLoc;
UnsignedOrNone PackSubstitutionIndex;
ConstraintSatisfaction &Satisfaction;
bool BuildExpression;
private:
ExprResult
@ -461,10 +461,11 @@ public:
ConstraintSatisfactionChecker(Sema &SemaRef, const NamedDecl *Template,
SourceLocation TemplateNameLoc,
UnsignedOrNone PackSubstitutionIndex,
ConstraintSatisfaction &Satisfaction)
ConstraintSatisfaction &Satisfaction,
bool BuildExpression)
: S(SemaRef), Template(Template), TemplateNameLoc(TemplateNameLoc),
PackSubstitutionIndex(PackSubstitutionIndex),
Satisfaction(Satisfaction) {}
Satisfaction(Satisfaction), BuildExpression(BuildExpression) {}
ExprResult Evaluate(const NormalizedConstraint &Constraint,
const MultiLevelTemplateArgumentList &MLTAL);
@ -821,9 +822,10 @@ ExprResult ConstraintSatisfactionChecker::EvaluateSlow(
Satisfaction.ContainsErrors = false;
ExprResult Expr =
ConstraintSatisfactionChecker(S, Template, TemplateNameLoc,
UnsignedOrNone(I), Satisfaction)
UnsignedOrNone(I), Satisfaction,
/*BuildExpression=*/false)
.Evaluate(Constraint.getNormalizedPattern(), *SubstitutedArgs);
if (Expr.isUsable()) {
if (BuildExpression && Expr.isUsable()) {
if (Out.isUnset())
Out = Expr;
else
@ -834,7 +836,7 @@ ExprResult ConstraintSatisfactionChecker::EvaluateSlow(
Constraint.getBeginLoc(),
FPOptionsOverride{});
} else {
assert(!Satisfaction.IsSatisfied);
assert(!BuildExpression || !Satisfaction.IsSatisfied);
}
if (!Conjunction && Satisfaction.IsSatisfied) {
Satisfaction.Details.erase(Satisfaction.Details.begin() +
@ -985,7 +987,7 @@ ExprResult ConstraintSatisfactionChecker::Evaluate(
ExprResult E = Evaluate(Constraint.getNormalizedConstraint(), MLTAL);
if (!E.isUsable()) {
if (E.isInvalid()) {
Satisfaction.Details.insert(Satisfaction.Details.begin() + Size, ConceptId);
return E;
}
@ -1041,7 +1043,7 @@ ExprResult ConstraintSatisfactionChecker::Evaluate(
if (Conjunction && (!Satisfaction.IsSatisfied || Satisfaction.ContainsErrors))
return LHS;
if (!Conjunction && LHS.isUsable() && Satisfaction.IsSatisfied &&
if (!Conjunction && !LHS.isInvalid() && Satisfaction.IsSatisfied &&
!Satisfaction.ContainsErrors)
return LHS;
@ -1050,12 +1052,15 @@ ExprResult ConstraintSatisfactionChecker::Evaluate(
ExprResult RHS = Evaluate(Constraint.getRHS(), MLTAL);
if (RHS.isUsable() && Satisfaction.IsSatisfied &&
if (!Conjunction && !RHS.isInvalid() && Satisfaction.IsSatisfied &&
!Satisfaction.ContainsErrors)
Satisfaction.Details.erase(Satisfaction.Details.begin() +
EffectiveDetailEndIndex,
Satisfaction.Details.end());
if (!BuildExpression)
return Satisfaction.ContainsErrors ? ExprError() : ExprEmpty();
if (!LHS.isUsable())
return RHS;
@ -1136,10 +1141,11 @@ static bool CheckConstraintSatisfaction(
Template, /*CSE=*/nullptr,
S.ArgPackSubstIndex);
ExprResult Res =
ConstraintSatisfactionChecker(S, Template, TemplateIDRange.getBegin(),
S.ArgPackSubstIndex, Satisfaction)
.Evaluate(*C, TemplateArgsLists);
ExprResult Res = ConstraintSatisfactionChecker(
S, Template, TemplateIDRange.getBegin(),
S.ArgPackSubstIndex, Satisfaction,
/*BuildExpression=*/ConvertedExpr != nullptr)
.Evaluate(*C, TemplateArgsLists);
if (Res.isInvalid())
return true;

View File

@ -157,66 +157,55 @@ static_assert(And1<S, S>() == 1);
// FIXME: The diagnostics are not so great
static_assert(And1<int>() == 1); // expected-error {{no matching function for call to 'And1'}}
// expected-note@#and1 {{candidate template ignored: constraints not satisfied [with T = <int>]}}
// expected-note@#and1 {{because 'typename T::type' does not satisfy 'C'}}
// expected-note@#C {{because 'T' does not satisfy 'A'}}
// expected-note@#and1 {{because substituted constraint expression is ill-formed: type 'int' cannot be used prior to '::' because it has no members}}
static_assert(And1<S, int>() == 1); // expected-error {{no matching function for call to 'And1'}}
// expected-note@#and1 {{candidate template ignored: constraints not satisfied [with T = <S, int>]}}
// expected-note@#and1 {{because 'typename T::type' does not satisfy 'C'}}
// expected-note@#C {{because 'T' does not satisfy 'A'}}
// expected-note@#and1 {{because substituted constraint expression is ill-formed: type 'int' cannot be used prior to '::' because it has no members}}
static_assert(And1<int, S>() == 1); // expected-error {{no matching function for call to 'And1'}}
// expected-note@#and1 {{candidate template ignored: constraints not satisfied [with T = <int, S>]}}
// expected-note@#and1 {{because 'typename T::type' does not satisfy 'C'}}
// expected-note@#C {{because 'T' does not satisfy 'A'}}
// expected-note@#and1 {{because substituted constraint expression is ill-formed: type 'int' cannot be used prior to '::' because it has no members}}
static_assert(And2<S>() == 2);
static_assert(And2<S, S>() == 2);
static_assert(And2<int>() == 2); // expected-error {{no matching function for call to 'And2'}}
// expected-note@#and2 {{candidate template ignored: constraints not satisfied [with T = int, U = <>]}}
// expected-note@#and2 {{because 'typename U::type' does not satisfy 'C'}}
// expected-note@#C {{because 'T' does not satisfy 'A'}}
// expected-note@#and2 {{because substituted constraint expression is ill-formed: type 'int' cannot be used prior to '::' because it has no members}}
static_assert(And2<int, int>() == 2); // expected-error {{no matching function for call to 'And2'}}
// expected-note@#and2 {{candidate template ignored: constraints not satisfied [with T = S, U = <int>]}} \
// expected-note@#and2 {{because 'typename U::type' does not satisfy 'C'}}
// expected-note@#C {{because 'T' does not satisfy 'A'}}
// expected-note@#and2 {{because substituted constraint expression is ill-formed: type 'int' cannot be used prior to '::' because it has no members}}
static_assert(And2<S, int>() == 2); // expected-error {{no matching function for call to 'And2'}}
// expected-note@#and2 {{candidate template ignored: constraints not satisfied [with T = int, U = <S>]}}
// expected-note@#and2 {{because 'typename T::type' does not satisfy 'C'}}
// expected-note@#C {{because 'T' does not satisfy 'A'}}
// expected-note@#and2 {{because substituted constraint expression is ill-formed: type 'int' cannot be used prior to '::' because it has no members}}
static_assert(And2<int, S>() == 2); // expected-error {{no matching function for call to 'And2'}}
// expected-note@#and2 {{candidate template ignored: constraints not satisfied [with T = int, U = <int>]}}
// expected-note@#and2 {{because 'typename T::type' does not satisfy 'C'}}
// expected-note@#C {{because 'T' does not satisfy 'A'}}
// expected-note@#and2 {{because substituted constraint expression is ill-formed: type 'int' cannot be used prior to '::' because it has no members}}
static_assert(And3<S>() == 3);
static_assert(And3<S, S>() == 3);
static_assert(And3<int>() == 3); // expected-error {{no matching function for call to 'And3'}}
// expected-note@#and3 {{candidate template ignored: constraints not satisfied [with T = int, U = <>]}}
// expected-note@#and3 {{because 'typename T::type' does not satisfy 'C'}}
// expected-note@#C {{because 'T' does not satisfy 'A'}}
// expected-note@#and3 {{because substituted constraint expression is ill-formed: type 'int' cannot be used prior to '::' because it has no members}}
static_assert(And3<int, int>() == 3); // expected-error {{no matching function for call to 'And3'}}
// expected-note@#and3 {{candidate template ignored: constraints not satisfied [with T = int, U = <int>]}}
// expected-note@#and3 {{because 'typename T::type' does not satisfy 'C'}}
// expected-note@#C {{because 'T' does not satisfy 'A'}}
// expected-note@#and3 {{because substituted constraint expression is ill-formed: type 'int' cannot be used prior to '::' because it has no members}}
static_assert(And3<S, int>() == 3); // expected-error {{no matching function for call to 'And3'}}
// expected-note@#and3 {{candidate template ignored: constraints not satisfied [with T = S, U = <int>]}}
// expected-note@#and3 {{because 'typename U::type' does not satisfy 'C'}}
// expected-note@#C {{because 'T' does not satisfy 'A'}}
// expected-note@#and3 {{because substituted constraint expression is ill-formed: type 'int' cannot be used prior to '::' because it has no members}}
static_assert(And3<int, S>() == 3); // expected-error {{no matching function for call to 'And3'}}
// expected-note@#and3 {{candidate template ignored: constraints not satisfied [with T = int, U = <S>]}}
// expected-note@#and3 {{because 'typename T::type' does not satisfy 'C'}}
// expected-note@#C {{because 'T' does not satisfy 'A'}}
// expected-note@#and3 {{because substituted constraint expression is ill-formed: type 'int' cannot be used prior to '::' because it has no members}}
static_assert(Or1<>() == 1); // expected-error {{no matching function for call to 'Or1'}}
@ -227,8 +216,7 @@ static_assert(Or1<S, int>() == 1);
static_assert(Or1<S, S>() == 1);
static_assert(Or1<int>() == 1); // expected-error {{no matching function for call to 'Or1'}}
// expected-note@#or1 {{candidate template ignored: constraints not satisfied}}
// expected-note@#or1 {{because 'typename T::type' does not satisfy 'C'}}
// expected-note@#C {{because 'T' does not satisfy 'A'}}
// expected-note@#or1 {{because substituted constraint expression is ill-formed: type 'int' cannot be used prior to '::' because it has no members}}
static_assert(Or2<S>() == 2);
static_assert(Or2<int, S>() == 2);
@ -236,16 +224,14 @@ static_assert(Or2<S, int>() == 2);
static_assert(Or2<S, S>() == 2);
static_assert(Or2<int>() == 2); // expected-error {{no matching function for call to 'Or2'}}
// expected-note@#or2 {{candidate template ignored: constraints not satisfied [with T = int, U = <>]}}
// expected-note@#or2 {{because 'typename T::type' does not satisfy 'C'}}
// expected-note@#C {{because 'T' does not satisfy 'A'}}
// expected-note@#or2 {{because substituted constraint expression is ill-formed: type 'int' cannot be used prior to '::' because it has no members}}
static_assert(Or3<S>() == 3);
static_assert(Or3<int, S>() == 3);
static_assert(Or3<S, int>() == 3);
static_assert(Or3<S, S>() == 3);
static_assert(Or3<int>() == 3); // expected-error {{no matching function for call to 'Or3'}}
// expected-note@#or3 {{candidate template ignored: constraints not satisfied}}
// expected-note@#or3 {{because 'typename T::type' does not satisfy 'C'}}
// expected-note@#C {{because 'T' does not satisfy 'A'}}
// expected-note@#or3 {{because substituted constraint expression is ill-formed: type 'int' cannot be used prior to '::' because it has no members}}
}
namespace bool_conversion_break {

View File

@ -82,7 +82,6 @@ auto it = begin(rng); // #BEGIN_CALL
// expected-error@#BEGIN_CALL {{no matching function for call to 'begin'}}
// expected-note@#NOTINF_BEGIN {{candidate function}}
// expected-note@#INF_BEGIN{{candidate template ignored: constraints not satisfied}}
// expected-note@#INF_BEGIN{{because 'Inf auto' does not satisfy 'Inf}}
}
} // namespace DirectRecursiveCheck