diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 04ec2cfef679c..97da416899aa8 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -362,7 +362,8 @@ Bug Fixes to C++ Support - Fixed a Clang regression in C++20 mode where unresolved dependent call expressions were created inside non-dependent contexts (#GH122892) - Clang now emits the ``-Wunused-variable`` warning when some structured bindings are unused and the ``[[maybe_unused]]`` attribute is not applied. (#GH125810) -- Clang now issues an error when placement new is used to modify a const-qualified variable +- Clang no longer crashes when establishing subsumption between some constraint expressions. (#GH122581) +- Clang now issues an error when placement new is used to modify a const-qualified variable in a ``constexpr`` function. (#GH131432) Bug Fixes to AST Handling diff --git a/clang/include/clang/Sema/SemaConcept.h b/clang/include/clang/Sema/SemaConcept.h index 5c599a70532f6..fda22b779c636 100644 --- a/clang/include/clang/Sema/SemaConcept.h +++ b/clang/include/clang/Sema/SemaConcept.h @@ -14,13 +14,14 @@ #define LLVM_CLANG_SEMA_SEMACONCEPT_H #include "clang/AST/ASTConcept.h" #include "clang/AST/ASTContext.h" -#include "clang/AST/Expr.h" #include "clang/AST/DeclTemplate.h" +#include "clang/AST/Expr.h" #include "clang/Basic/SourceLocation.h" +#include "llvm/ADT/FoldingSet.h" #include "llvm/ADT/PointerUnion.h" +#include "llvm/ADT/STLFunctionalExtras.h" #include "llvm/ADT/SmallVector.h" #include -#include #include namespace clang { @@ -56,49 +57,10 @@ struct alignas(ConstraintAlignment) AtomicConstraint { } return true; } - - bool subsumes(ASTContext &C, const AtomicConstraint &Other) const { - // C++ [temp.constr.order] p2 - // - an atomic constraint A subsumes another atomic constraint B - // if and only if the A and B are identical [...] - // - // C++ [temp.constr.atomic] p2 - // Two atomic constraints are identical if they are formed from the - // same expression and the targets of the parameter mappings are - // equivalent according to the rules for expressions [...] - - // We do not actually substitute the parameter mappings into the - // constraint expressions, therefore the constraint expressions are - // the originals, and comparing them will suffice. - if (ConstraintExpr != Other.ConstraintExpr) - return false; - - // Check that the parameter lists are identical - return hasMatchingParameterMapping(C, Other); - } }; -struct alignas(ConstraintAlignment) FoldExpandedConstraint; - -using NormalFormConstraint = - llvm::PointerUnion; -struct NormalizedConstraint; -using NormalForm = - llvm::SmallVector, 4>; - -// A constraint is in conjunctive normal form when it is a conjunction of -// clauses where each clause is a disjunction of atomic constraints. For atomic -// constraints A, B, and C, the constraint A  ∧ (B  ∨ C) is in conjunctive -// normal form. -NormalForm makeCNF(const NormalizedConstraint &Normalized); - -// A constraint is in disjunctive normal form when it is a disjunction of -// clauses where each clause is a conjunction of atomic constraints. For atomic -// constraints A, B, and C, the disjunctive normal form of the constraint A -//  ∧ (B  ∨ C) is (A  ∧ B)  ∨ (A  ∧ C). -NormalForm makeDNF(const NormalizedConstraint &Normalized); - struct alignas(ConstraintAlignment) NormalizedConstraintPair; +struct alignas(ConstraintAlignment) FoldExpandedConstraint; /// \brief A normalized constraint, as defined in C++ [temp.constr.normal], is /// either an atomic constraint, a conjunction of normalized constraints or a @@ -170,10 +132,6 @@ struct alignas(ConstraintAlignment) FoldExpandedConstraint { const Expr *Pattern) : Kind(K), Constraint(std::move(C)), Pattern(Pattern) {}; - template - bool subsumes(const FoldExpandedConstraint &Other, - const AtomicSubsumptionEvaluator &E) const; - static bool AreCompatibleForSubsumption(const FoldExpandedConstraint &A, const FoldExpandedConstraint &B); }; @@ -182,90 +140,102 @@ const NormalizedConstraint *getNormalizedAssociatedConstraints( Sema &S, NamedDecl *ConstrainedDecl, ArrayRef AssociatedConstraints); -template -bool subsumes(const NormalForm &PDNF, const NormalForm &QCNF, - const AtomicSubsumptionEvaluator &E) { - // C++ [temp.constr.order] p2 - // Then, P subsumes Q if and only if, for every disjunctive clause Pi in the - // disjunctive normal form of P, Pi subsumes every conjunctive clause Qj in - // the conjuctive normal form of Q, where [...] - for (const auto &Pi : PDNF) { - for (const auto &Qj : QCNF) { - // C++ [temp.constr.order] p2 - // - [...] a disjunctive clause Pi subsumes a conjunctive clause Qj if - // and only if there exists an atomic constraint Pia in Pi for which - // there exists an atomic constraint, Qjb, in Qj such that Pia - // subsumes Qjb. - bool Found = false; - for (NormalFormConstraint Pia : Pi) { - for (NormalFormConstraint Qjb : Qj) { - if (isa(Pia) && - isa(Qjb)) { - if (cast(Pia)->subsumes( - *cast(Qjb), E)) { - Found = true; - break; - } - } else if (isa(Pia) && - isa(Qjb)) { - if (E(*cast(Pia), - *cast(Qjb))) { - Found = true; - break; - } - } - } - if (Found) - break; - } - if (!Found) - return false; - } - } - return true; -} - -template -bool subsumes(Sema &S, NamedDecl *DP, ArrayRef P, NamedDecl *DQ, - ArrayRef Q, bool &Subsumes, - const AtomicSubsumptionEvaluator &E) { - // C++ [temp.constr.order] p2 - // In order to determine if a constraint P subsumes a constraint Q, P is - // transformed into disjunctive normal form, and Q is transformed into - // conjunctive normal form. [...] - const NormalizedConstraint *PNormalized = - getNormalizedAssociatedConstraints(S, DP, P); - if (!PNormalized) - return true; - NormalForm PDNF = makeDNF(*PNormalized); +/// \brief SubsumptionChecker establishes subsumption +/// between two set of constraints. +class SubsumptionChecker { +public: + using SubsumptionCallable = llvm::function_ref; - const NormalizedConstraint *QNormalized = - getNormalizedAssociatedConstraints(S, DQ, Q); - if (!QNormalized) - return true; - NormalForm QCNF = makeCNF(*QNormalized); - - Subsumes = subsumes(PDNF, QCNF, E); - return false; -} - -template -bool FoldExpandedConstraint::subsumes( - const FoldExpandedConstraint &Other, - const AtomicSubsumptionEvaluator &E) const { + SubsumptionChecker(Sema &SemaRef, SubsumptionCallable Callable = {}); - // [C++26] [temp.constr.order] - // a fold expanded constraint A subsumes another fold expanded constraint B if - // they are compatible for subsumption, have the same fold-operator, and the - // constraint of A subsumes that of B + std::optional Subsumes(NamedDecl *DP, ArrayRef P, + NamedDecl *DQ, ArrayRef Q); - if (Kind != Other.Kind || !AreCompatibleForSubsumption(*this, Other)) - return false; + bool Subsumes(const NormalizedConstraint *P, const NormalizedConstraint *Q); - NormalForm PDNF = makeDNF(this->Constraint); - NormalForm QCNF = makeCNF(Other.Constraint); - return clang::subsumes(PDNF, QCNF, E); -} +private: + Sema &SemaRef; + SubsumptionCallable Callable; + + // Each Literal has a unique value that is enough to establish + // its identity. + // Some constraints (fold expended) require special subsumption + // handling logic beyond comparing values, so we store a flag + // to let us quickly dispatch to each kind of variable. + struct Literal { + enum Kind { Atomic, FoldExpanded }; + + unsigned Value : 16; + LLVM_PREFERRED_TYPE(Kind) + unsigned Kind : 1; + + bool operator==(const Literal &Other) const { return Value == Other.Value; } + bool operator<(const Literal &Other) const { return Value < Other.Value; } + }; + using Clause = llvm::SmallVector; + using Formula = llvm::SmallVector; + + struct CNFFormula : Formula { + static constexpr auto Kind = NormalizedConstraint::CCK_Conjunction; + using Formula::Formula; + }; + struct DNFFormula : Formula { + static constexpr auto Kind = NormalizedConstraint::CCK_Disjunction; + using Formula::Formula; + }; + + struct MappedAtomicConstraint { + AtomicConstraint *Constraint; + Literal ID; + }; + + struct FoldExpendedConstraintKey { + FoldExpandedConstraint::FoldOperatorKind Kind; + AtomicConstraint *Constraint; + Literal ID; + }; + + llvm::DenseMap> + AtomicMap; + + llvm::DenseMap> FoldMap; + + // A map from a literal to a corresponding associated constraint. + // We do not have enough bits left for a pointer union here :( + llvm::DenseMap ReverseMap; + + // Fold expanded constraints ask us to recursively establish subsumption. + // This caches the result. + llvm::SmallDenseMap< + std::pair, + bool> + FoldSubsumptionCache; + + // Each is represented as a single ID. + // This is intentionally kept small we can't handle a large number of + // constraints anyway. + uint16_t NextID; + + bool Subsumes(const DNFFormula &P, const CNFFormula &Q); + bool Subsumes(Literal A, Literal B); + bool Subsumes(const FoldExpandedConstraint *A, + const FoldExpandedConstraint *B); + bool DNFSubsumes(const Clause &P, const Clause &Q); + + CNFFormula CNF(const NormalizedConstraint &C); + DNFFormula DNF(const NormalizedConstraint &C); + + template + FormulaType Normalize(const NormalizedConstraint &C); + void AddUniqueClauseToFormula(Formula &F, Clause C); + + Literal find(AtomicConstraint *); + Literal find(FoldExpandedConstraint *); + + uint16_t getNewLiteralId(); +}; } // clang diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp index 57dc4154a537f..e7e0b4cfb72a7 100644 --- a/clang/lib/Sema/SemaConcept.cpp +++ b/clang/lib/Sema/SemaConcept.cpp @@ -1726,71 +1726,6 @@ bool FoldExpandedConstraint::AreCompatibleForSubsumption( return false; } -NormalForm clang::makeCNF(const NormalizedConstraint &Normalized) { - if (Normalized.isAtomic()) - return {{Normalized.getAtomicConstraint()}}; - - else if (Normalized.isFoldExpanded()) - return {{Normalized.getFoldExpandedConstraint()}}; - - NormalForm LCNF = makeCNF(Normalized.getLHS()); - NormalForm RCNF = makeCNF(Normalized.getRHS()); - if (Normalized.getCompoundKind() == NormalizedConstraint::CCK_Conjunction) { - LCNF.reserve(LCNF.size() + RCNF.size()); - while (!RCNF.empty()) - LCNF.push_back(RCNF.pop_back_val()); - return LCNF; - } - - // Disjunction - NormalForm Res; - Res.reserve(LCNF.size() * RCNF.size()); - for (auto &LDisjunction : LCNF) - for (auto &RDisjunction : RCNF) { - NormalForm::value_type Combined; - Combined.reserve(LDisjunction.size() + RDisjunction.size()); - std::copy(LDisjunction.begin(), LDisjunction.end(), - std::back_inserter(Combined)); - std::copy(RDisjunction.begin(), RDisjunction.end(), - std::back_inserter(Combined)); - Res.emplace_back(Combined); - } - return Res; -} - -NormalForm clang::makeDNF(const NormalizedConstraint &Normalized) { - if (Normalized.isAtomic()) - return {{Normalized.getAtomicConstraint()}}; - - else if (Normalized.isFoldExpanded()) - return {{Normalized.getFoldExpandedConstraint()}}; - - NormalForm LDNF = makeDNF(Normalized.getLHS()); - NormalForm RDNF = makeDNF(Normalized.getRHS()); - if (Normalized.getCompoundKind() == NormalizedConstraint::CCK_Disjunction) { - LDNF.reserve(LDNF.size() + RDNF.size()); - while (!RDNF.empty()) - LDNF.push_back(RDNF.pop_back_val()); - return LDNF; - } - - // Conjunction - NormalForm Res; - Res.reserve(LDNF.size() * RDNF.size()); - for (auto &LConjunction : LDNF) { - for (auto &RConjunction : RDNF) { - NormalForm::value_type Combined; - Combined.reserve(LConjunction.size() + RConjunction.size()); - std::copy(LConjunction.begin(), LConjunction.end(), - std::back_inserter(Combined)); - std::copy(RConjunction.begin(), RConjunction.end(), - std::back_inserter(Combined)); - Res.emplace_back(Combined); - } - } - return Res; -} - bool Sema::IsAtLeastAsConstrained(NamedDecl *D1, MutableArrayRef AC1, NamedDecl *D2, @@ -1842,18 +1777,21 @@ bool Sema::IsAtLeastAsConstrained(NamedDecl *D1, } } - if (clang::subsumes( - *this, D1, AC1, D2, AC2, Result, - [this](const AtomicConstraint &A, const AtomicConstraint &B) { - return A.subsumes(Context, B); - })) + SubsumptionChecker SC(*this); + std::optional Subsumes = SC.Subsumes(D1, AC1, D2, AC2); + if (!Subsumes) { + // Normalization failed return true; - SubsumptionCache.try_emplace(Key, Result); + } + Result = *Subsumes; + SubsumptionCache.try_emplace(Key, *Subsumes); return false; } -bool Sema::MaybeEmitAmbiguousAtomicConstraintsDiagnostic(NamedDecl *D1, - ArrayRef AC1, NamedDecl *D2, ArrayRef AC2) { +bool Sema::MaybeEmitAmbiguousAtomicConstraintsDiagnostic( + NamedDecl *D1, ArrayRef AC1, NamedDecl *D2, + ArrayRef AC2) { + if (isSFINAEContext()) // No need to work here because our notes would be discarded. return false; @@ -1861,32 +1799,27 @@ bool Sema::MaybeEmitAmbiguousAtomicConstraintsDiagnostic(NamedDecl *D1, if (AC1.empty() || AC2.empty()) return false; - auto NormalExprEvaluator = - [this] (const AtomicConstraint &A, const AtomicConstraint &B) { - return A.subsumes(Context, B); - }; - const Expr *AmbiguousAtomic1 = nullptr, *AmbiguousAtomic2 = nullptr; - auto IdenticalExprEvaluator = - [&] (const AtomicConstraint &A, const AtomicConstraint &B) { - if (!A.hasMatchingParameterMapping(Context, B)) - return false; - const Expr *EA = A.ConstraintExpr, *EB = B.ConstraintExpr; - if (EA == EB) - return true; - - // Not the same source level expression - are the expressions - // identical? - llvm::FoldingSetNodeID IDA, IDB; - EA->Profile(IDA, Context, /*Canonical=*/true); - EB->Profile(IDB, Context, /*Canonical=*/true); - if (IDA != IDB) - return false; - - AmbiguousAtomic1 = EA; - AmbiguousAtomic2 = EB; - return true; - }; + auto IdenticalExprEvaluator = [&](const AtomicConstraint &A, + const AtomicConstraint &B) { + if (!A.hasMatchingParameterMapping(Context, B)) + return false; + const Expr *EA = A.ConstraintExpr, *EB = B.ConstraintExpr; + if (EA == EB) + return true; + + // Not the same source level expression - are the expressions + // identical? + llvm::FoldingSetNodeID IDA, IDB; + EA->Profile(IDA, Context, /*Canonical=*/true); + EB->Profile(IDB, Context, /*Canonical=*/true); + if (IDA != IDB) + return false; + + AmbiguousAtomic1 = EA; + AmbiguousAtomic2 = EB; + return true; + }; { // The subsumption checks might cause diagnostics @@ -1894,27 +1827,25 @@ bool Sema::MaybeEmitAmbiguousAtomicConstraintsDiagnostic(NamedDecl *D1, auto *Normalized1 = getNormalizedAssociatedConstraints(D1, AC1); if (!Normalized1) return false; - const NormalForm DNF1 = makeDNF(*Normalized1); - const NormalForm CNF1 = makeCNF(*Normalized1); auto *Normalized2 = getNormalizedAssociatedConstraints(D2, AC2); if (!Normalized2) return false; - const NormalForm DNF2 = makeDNF(*Normalized2); - const NormalForm CNF2 = makeCNF(*Normalized2); - - bool Is1AtLeastAs2Normally = - clang::subsumes(DNF1, CNF2, NormalExprEvaluator); - bool Is2AtLeastAs1Normally = - clang::subsumes(DNF2, CNF1, NormalExprEvaluator); - bool Is1AtLeastAs2 = clang::subsumes(DNF1, CNF2, IdenticalExprEvaluator); - bool Is2AtLeastAs1 = clang::subsumes(DNF2, CNF1, IdenticalExprEvaluator); + + SubsumptionChecker SC(*this); + + bool Is1AtLeastAs2Normally = SC.Subsumes(Normalized1, Normalized2); + bool Is2AtLeastAs1Normally = SC.Subsumes(Normalized2, Normalized1); + + SubsumptionChecker SC2(*this, IdenticalExprEvaluator); + bool Is1AtLeastAs2 = SC2.Subsumes(Normalized1, Normalized2); + bool Is2AtLeastAs1 = SC2.Subsumes(Normalized2, Normalized1); + if (Is1AtLeastAs2 == Is1AtLeastAs2Normally && Is2AtLeastAs1 == Is2AtLeastAs1Normally) // Same result - no ambiguity was caused by identical atomic expressions. return false; } - // A different result! Some ambiguous atomic constraint(s) caused a difference assert(AmbiguousAtomic1 && AmbiguousAtomic2); @@ -2001,3 +1932,258 @@ NormalizedConstraint::getFoldExpandedConstraint() const { "getFoldExpandedConstraint called on non-fold-expanded constraint."); return cast(Constraint); } + +// +// +// ------------------------ Subsumption ----------------------------------- +// +// + +template <> struct llvm::DenseMapInfo { + + static FoldingSetNodeID getEmptyKey() { + FoldingSetNodeID ID; + ID.AddInteger(std::numeric_limits::max()); + return ID; + } + + static FoldingSetNodeID getTombstoneKey() { + FoldingSetNodeID ID; + for (unsigned I = 0; I < sizeof(ID) / sizeof(unsigned); ++I) { + ID.AddInteger(std::numeric_limits::max()); + } + return ID; + } + + static unsigned getHashValue(const FoldingSetNodeID &Val) { + return Val.ComputeHash(); + } + + static bool isEqual(const FoldingSetNodeID &LHS, + const FoldingSetNodeID &RHS) { + return LHS == RHS; + } +}; + +SubsumptionChecker::SubsumptionChecker(Sema &SemaRef, + SubsumptionCallable Callable) + : SemaRef(SemaRef), Callable(Callable), NextID(1) {} + +uint16_t SubsumptionChecker::getNewLiteralId() { + assert((unsigned(NextID) + 1 < std::numeric_limits::max()) && + "too many constraints!"); + return NextID++; +} + +auto SubsumptionChecker::find(AtomicConstraint *Ori) -> Literal { + auto &Elems = AtomicMap[Ori->ConstraintExpr]; + // C++ [temp.constr.order] p2 + // - an atomic constraint A subsumes another atomic constraint B + // if and only if the A and B are identical [...] + // + // C++ [temp.constr.atomic] p2 + // Two atomic constraints are identical if they are formed from the + // same expression and the targets of the parameter mappings are + // equivalent according to the rules for expressions [...] + + // Because subsumption of atomic constraints is an identity + // relationship that does not require further analysis + // We cache the results such that if an atomic constraint literal + // subsumes another, their literal will be the same + + llvm::FoldingSetNodeID ID; + const auto &Mapping = Ori->ParameterMapping; + ID.AddBoolean(Mapping.has_value()); + if (Mapping) { + for (const TemplateArgumentLoc &TAL : *Mapping) { + SemaRef.getASTContext() + .getCanonicalTemplateArgument(TAL.getArgument()) + .Profile(ID, SemaRef.getASTContext()); + } + } + auto It = Elems.find(ID); + if (It == Elems.end()) { + It = + Elems + .insert({ID, MappedAtomicConstraint{Ori, Literal{getNewLiteralId(), + Literal::Atomic}}}) + .first; + ReverseMap[It->second.ID.Value] = Ori; + } + return It->getSecond().ID; +} + +auto SubsumptionChecker::find(FoldExpandedConstraint *Ori) -> Literal { + auto &Elems = FoldMap[Ori->Pattern]; + + FoldExpendedConstraintKey K; + K.Kind = Ori->Kind; + + auto It = llvm::find_if(Elems, [&K](const FoldExpendedConstraintKey &Other) { + return K.Kind == Other.Kind; + }); + if (It == Elems.end()) { + K.ID = {getNewLiteralId(), Literal::FoldExpanded}; + It = Elems.insert(Elems.end(), std::move(K)); + ReverseMap[It->ID.Value] = Ori; + } + return It->ID; +} + +auto SubsumptionChecker::CNF(const NormalizedConstraint &C) -> CNFFormula { + return SubsumptionChecker::Normalize(C); +} +auto SubsumptionChecker::DNF(const NormalizedConstraint &C) -> DNFFormula { + return SubsumptionChecker::Normalize(C); +} + +/// +/// \brief SubsumptionChecker::Normalize +/// +/// Normalize a formula to Conjunctive Normal Form or +/// Disjunctive normal form. +/// +/// Each Atomic (and Fold Expanded) constraint gets represented by +/// a single id to reduce space. +/// +/// To minimize risks of exponential blow up, if two atomic +/// constraints subsumes each other (same constraint and mapping), +/// they are represented by the same literal. +/// +template +FormulaType SubsumptionChecker::Normalize(const NormalizedConstraint &NC) { + FormulaType Res; + + auto Add = [&, this](Clause C) { + // Sort each clause and remove duplicates for faster comparisons + llvm::sort(C); + C.erase(llvm::unique(C), C.end()); + AddUniqueClauseToFormula(Res, std::move(C)); + }; + + if (NC.isAtomic()) + return {{find(NC.getAtomicConstraint())}}; + + if (NC.isFoldExpanded()) + return {{find(NC.getFoldExpandedConstraint())}}; + + FormulaType Left, Right; + SemaRef.runWithSufficientStackSpace(SourceLocation(), [&] { + Left = Normalize(NC.getLHS()); + Right = Normalize(NC.getRHS()); + }); + + if (NC.getCompoundKind() == FormulaType::Kind) { + Res = std::move(Left); + Res.reserve(Left.size() + Right.size()); + std::for_each(std::make_move_iterator(Right.begin()), + std::make_move_iterator(Right.end()), Add); + return Res; + } + + Res.reserve(Left.size() * Right.size()); + for (const auto <ransform : Left) { + for (const auto &RTransform : Right) { + Clause Combined; + Combined.reserve(LTransform.size() + RTransform.size()); + llvm::copy(LTransform, std::back_inserter(Combined)); + llvm::copy(RTransform, std::back_inserter(Combined)); + Add(std::move(Combined)); + } + } + return Res; +} + +void SubsumptionChecker::AddUniqueClauseToFormula(Formula &F, Clause C) { + for (auto &Other : F) { + if (llvm::equal(C, Other)) + return; + } + F.push_back(C); +} + +std::optional SubsumptionChecker::Subsumes(NamedDecl *DP, + ArrayRef P, + NamedDecl *DQ, + ArrayRef Q) { + const NormalizedConstraint *PNormalized = + getNormalizedAssociatedConstraints(SemaRef, DP, P); + if (!PNormalized) + return std::nullopt; + + const NormalizedConstraint *QNormalized = + getNormalizedAssociatedConstraints(SemaRef, DQ, Q); + if (!QNormalized) + return std::nullopt; + + return Subsumes(PNormalized, QNormalized); +} + +bool SubsumptionChecker::Subsumes(const NormalizedConstraint *P, + const NormalizedConstraint *Q) { + + DNFFormula DNFP = DNF(*P); + CNFFormula CNFQ = CNF(*Q); + return Subsumes(DNFP, CNFQ); +} + +bool SubsumptionChecker::Subsumes(const DNFFormula &PDNF, + const CNFFormula &QCNF) { + for (const auto &Pi : PDNF) { + for (const auto &Qj : QCNF) { + // C++ [temp.constr.order] p2 + // - [...] a disjunctive clause Pi subsumes a conjunctive clause Qj if + // and only if there exists an atomic constraint Pia in Pi for which + // there exists an atomic constraint, Qjb, in Qj such that Pia + // subsumes Qjb. + if (!DNFSubsumes(Pi, Qj)) + return false; + } + } + return true; +} + +bool SubsumptionChecker::DNFSubsumes(const Clause &P, const Clause &Q) { + + return llvm::any_of(P, [&](Literal LP) { + return llvm::any_of(Q, [this, LP](Literal LQ) { return Subsumes(LP, LQ); }); + }); +} + +bool SubsumptionChecker::Subsumes(const FoldExpandedConstraint *A, + const FoldExpandedConstraint *B) { + std::pair Key{ + A, B}; + + auto It = FoldSubsumptionCache.find(Key); + if (It == FoldSubsumptionCache.end()) { + // C++ [temp.constr.order] + // a fold expanded constraint A subsumes another fold expanded + // constraint B if they are compatible for subsumption, have the same + // fold-operator, and the constraint of A subsumes that of B. + bool DoesSubsume = + A->Kind == B->Kind && + FoldExpandedConstraint::AreCompatibleForSubsumption(*A, *B) && + Subsumes(&A->Constraint, &B->Constraint); + It = FoldSubsumptionCache.try_emplace(std::move(Key), DoesSubsume).first; + } + return It->second; +} + +bool SubsumptionChecker::Subsumes(Literal A, Literal B) { + if (A.Kind != B.Kind) + return false; + switch (A.Kind) { + case Literal::Atomic: + if (!Callable) + return A.Value == B.Value; + return Callable( + *static_cast(ReverseMap[A.Value]), + *static_cast(ReverseMap[B.Value])); + case Literal::FoldExpanded: + return Subsumes( + static_cast(ReverseMap[A.Value]), + static_cast(ReverseMap[B.Value])); + } + llvm_unreachable("unknown literal kind"); +} diff --git a/clang/test/SemaCXX/concepts-subsumption.cpp b/clang/test/SemaCXX/concepts-subsumption.cpp new file mode 100644 index 0000000000000..d9d5535e532f4 --- /dev/null +++ b/clang/test/SemaCXX/concepts-subsumption.cpp @@ -0,0 +1,292 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 %s +// expected-no-diagnostics +namespace A { +template +concept C = true; + +template +requires C && C +void f() {} + +template +requires C && true +void f() {} + +void test() { f(); }; +} + +namespace B { +template +concept A = true; +template +concept B = true; + +template +requires (A && B) +constexpr int f() { return 0; } + +template +requires (A || B) +constexpr int f() { return 1; } + +static_assert(f() == 0); +} + +namespace GH122581 { +// Test that producing a Conjunctive Normal Form +// does not blow up exponentially. +// i.e, this should terminate reasonably quickly +// within a small memory footprint +template concept C0 = true; +template concept C1 = true; +template concept C2 = true; +template concept C3 = true; +template concept C4 = true; +template concept C5 = true; +template concept C6 = true; +template concept C7 = true; +template concept C8 = true; +template concept C9 = true; + +template +concept Majority8 = + (C0 && C1 && C2 && C3) || + (C0 && C1 && C2 && C4) || + (C0 && C1 && C2 && C5) || + (C0 && C1 && C2 && C6) || + (C0 && C1 && C2 && C7) || + (C0 && C1 && C3 && C4) || + (C0 && C1 && C3 && C5) || + (C0 && C1 && C3 && C6) || + (C0 && C1 && C3 && C7) || + (C0 && C1 && C4 && C5) || + (C0 && C1 && C4 && C6) || + (C0 && C1 && C4 && C7) || + (C0 && C1 && C5 && C6) || + (C0 && C1 && C5 && C7) || + (C0 && C1 && C6 && C7) || + (C0 && C2 && C3 && C4) || + (C0 && C2 && C3 && C5) || + (C0 && C2 && C3 && C6) || + (C0 && C2 && C3 && C7) || + (C0 && C2 && C4 && C5) || + (C0 && C2 && C4 && C6) || + (C0 && C2 && C4 && C7) || + (C0 && C2 && C5 && C6) || + (C0 && C2 && C5 && C7) || + (C0 && C2 && C6 && C7) || + (C0 && C3 && C4 && C5) || + (C0 && C3 && C4 && C6) || + (C0 && C3 && C4 && C7) || + (C0 && C3 && C5 && C6) || + (C0 && C3 && C5 && C7) || + (C0 && C3 && C6 && C7) || + (C0 && C4 && C5 && C6) || + (C0 && C4 && C5 && C7) || + (C0 && C4 && C6 && C7) || + (C0 && C5 && C6 && C7) || + (C1 && C2 && C3 && C4) || + (C1 && C2 && C3 && C5) || + (C1 && C2 && C3 && C6) || + (C1 && C2 && C3 && C7) || + (C1 && C2 && C4 && C5) || + (C1 && C2 && C4 && C6) || + (C1 && C2 && C4 && C7) || + (C1 && C2 && C5 && C6) || + (C1 && C2 && C5 && C7) || + (C1 && C2 && C6 && C7) || + (C1 && C3 && C4 && C5) || + (C1 && C3 && C4 && C6) || + (C1 && C3 && C4 && C7) || + (C1 && C3 && C5 && C6) || + (C1 && C3 && C5 && C7) || + (C1 && C3 && C6 && C7) || + (C1 && C4 && C5 && C6) || + (C1 && C4 && C5 && C7) || + (C1 && C4 && C6 && C7) || + (C1 && C5 && C6 && C7) || + (C2 && C3 && C4 && C5) || + (C2 && C3 && C4 && C6) || + (C2 && C3 && C4 && C7) || + (C2 && C3 && C5 && C6) || + (C2 && C3 && C5 && C7) || + (C2 && C3 && C6 && C7) || + (C2 && C4 && C5 && C6) || + (C2 && C4 && C5 && C7) || + (C2 && C4 && C6 && C7) || + (C2 && C5 && C6 && C7) || + (C3 && C4 && C5 && C6) || + (C3 && C4 && C5 && C7) || + (C3 && C4 && C6 && C7) || + (C3 && C5 && C6 && C7) || + (C4 && C5 && C6 && C7); + +template concept Y = C0 || Majority8; +template concept Z = Majority8 && C1; + +constexpr int foo(Majority8 auto x) { return 10; } +constexpr int foo(Y auto y) { return 20; } +constexpr int foo(Z auto y) { return 30; } +static_assert(foo(0) == 30); +} + +namespace WhateverThisIs { +template concept C0 = true; +template concept C1 = true; +template concept C2 = true; +template concept C3 = true; +template concept C4 = true; + +template +concept X = + (C0 || C1 || C2) && + (C0 || C1 || C3) && + (C0 || C1 || C4) && + (C0 || C2 || C3) && + (C0 || C2 || C4) && + (C0 || C3 || C4) && + (C1 || C2 || C3) && + (C1 || C2 || C4) && + (C1 || C3 || C4) && + (C2 || C3 || C4); + +template concept Y = C0 && X; + +template concept Z = Y && C1; + +constexpr int foo(X auto x) { return 10; } +constexpr int foo(Y auto y) { return 20; } +constexpr int foo(Z auto y) { return 30; } + +static_assert(foo(0) == 30); +} + +namespace WAT{ +// randomly generated formulas misshandled by clang 20, +// and some other compilers. There is no particular meaning +// to it except to stress-test the compiler. + +template +concept Z0 = true; + +template +concept Z1 = true; + +template +concept Z2 = true; + +template +concept Z3 = true; + +template +concept Z4 = true; + +template +concept Z5 = true; + +template +concept Z6 = true; + +template +concept Z7 = true; + +template +concept Z8 = true; + +template +concept Z9 = true; + +template +concept Z10 = true; + +template +concept Z11 = true; + +template +concept Z12 = true; + +template +concept Z13 = true; + +template +concept Z14 = true; + +template +concept Z15 = true; + +template +concept Z16 = true; + +template +concept Z17 = true; + +template +concept Z18 = true; + +template +concept Z19 = true; + +namespace T1 { +template +concept X = ((((((((Z13 || (Z2 || Z10)) && (Z2 && (Z6 && ((Z7 && Z13) && Z0)))) && (Z13 || +(Z12 && Z8))) && (Z9 || (Z2 && Z17))) || Z2) && ((((Z17 || Z6) && (((Z6 || Z4) || Z9) +&& Z13)) || ((Z14 || Z10) || Z3)) || (Z8 || ((Z19 && (Z3 && Z14)) && ((Z5 || (Z3 && +Z5)) || (Z7 && Z13)))))) || ((((Z14 && (Z2 && Z1)) || ((Z17 && Z12) && (Z0 || ((((Z9 || +(Z6 && Z16)) && Z19) && (Z6 && (Z12 && Z17))) && (Z19 && Z8))))) || (((Z10 || Z17) && +Z1) && ((Z16 && (Z15 || Z5)) || ((Z4 && Z5) || ((Z1 || Z4) || Z2))))) && (((Z12 && (Z5 +&& Z10)) || ((Z4 && Z18) && Z0)) || ((((Z10 || Z0) && Z18) || (Z15 && ((Z11 && Z5) && +Z6))) && Z2)))) && ((((((((((Z8 && Z13) && Z7) && Z18) && ((((Z7 && Z11) || (Z19 && Z6)) +|| Z13) && Z15)) || (Z1 || Z15)) || (Z9 && (Z6 || Z10))) || Z0) && (((Z14 || Z4) && +(Z4 || ((Z4 && Z10) && Z11))) || Z4)) && ((((((Z8 && ((Z1 && (Z16 && (Z0 && Z6))) && +(Z1 && Z10))) && ((Z18 && Z3) || ((Z14 && Z1) || Z15))) && (((Z19 || Z17) || ((Z17 && +(Z9 && Z19)) || Z6)) || (((Z4 || (((Z4 || Z9) && Z6) && Z2)) || ((Z17 && (Z16 && ((Z14 +&& Z10) && Z17))) || Z9)) && (Z5 && Z6)))) && (((Z3 && Z14) || Z5) && Z8)) && ((((Z10 || +(Z17 && Z8)) || ((Z16 && (((Z12 && Z16) || Z18) || (Z4 && Z13))) || (Z17 && Z10))) || +((((Z9 && ((Z7 || Z2) && Z15)) || ((Z18 && Z13) || (Z4 || Z14))) || (((Z7 || (Z10 && +(Z14 && Z18))) && (Z9 || Z5)) || (Z8 && ((Z14 || Z11) || ((Z4 || Z2) && (Z7 && +Z5)))))) && (((Z14 && (Z13 && Z10)) || Z8) && (((((Z7 || (Z8 && Z14)) || Z0) && Z0) || +Z17) || Z5)))) && (Z16 && Z4))) && (((Z1 && Z12) || ((Z17 || Z4) || (Z15 || (Z6 || +Z8)))) || (((Z2 || Z19) && Z5) && Z1)))) || ((((Z9 || (Z12 || Z6)) && (Z5 || Z12)) && +((Z1 || Z8) || (Z18 && Z19))) || ((Z11 && Z17) || (Z5 && Z12))))); + +template +concept Y = Z0 && X; + +constexpr int foo(X auto x) { return 1; } +constexpr int foo(Y auto y) { return 2; } +static_assert(foo(0) == 2); +} + +namespace T3 { + +template +concept X = (((Z2 && ((Z7 || (Z8 && (Z6 && Z4))) && ((Z1 && Z3) || ((Z1 && (Z7 && Z2)) && +Z1)))) && ((Z7 || (((Z6 || Z0) || (Z5 || Z3)) && Z3)) && ((Z6 || ((((Z6 && Z8) && (Z8 +&& Z3)) || (Z6 && Z5)) && (Z6 || (Z3 && (Z3 || Z8))))) && ((((Z3 || (Z3 && (Z6 || +Z8))) && Z3) && Z9) || ((Z7 || Z6) || ((Z3 && (Z4 && (Z0 && Z3))) && (((Z5 && (Z1 || +Z5)) || Z3) && (((Z7 || Z5) || ((Z9 || Z1) && ((Z9 && Z0) || Z0))) && (Z5 && +Z7))))))))) || (((((Z5 || Z0) || (Z7 && (Z8 && (Z9 || (Z6 && Z1))))) || (((Z6 || Z3) || +Z1) && Z3)) || (((Z9 && ((((Z9 || (Z9 && (((Z7 || ((Z4 || Z3) || Z8)) && Z3) && (Z1 && +Z3)))) || ((Z1 && ((Z8 && (Z9 && Z6)) && (Z1 || Z5))) || Z0)) && Z2) && ((Z1 || (Z0 || +Z7)) || (Z9 && Z4)))) || (Z4 || Z3)) && ((Z3 && Z9) || ((Z6 || Z8) && (Z7 && (Z9 || +(Z3 || Z7))))))) && (Z2 && (Z7 || Z3)))); + +template +concept Y = X && ((Z2 && (((Z6 || Z5) || Z1) && (Z4 || ((Z9 || (Z2 || Z5)) || Z7)))) || +((((((Z9 || (Z1 || Z3)) && Z5) || ((Z5 || Z0) || (Z2 && Z1))) || Z3) || (((Z0 && ((Z4 +&& (((Z3 && Z0) || (Z1 || Z5)) || Z6)) || ((Z7 || (Z1 || Z8)) || Z8))) && ((Z6 || (Z6 +|| Z9)) && (Z1 || Z0))) || (Z5 || (((Z8 || Z5) && (((((((Z3 || Z2) || Z6) || ((Z6 || +Z4) || ((Z1 && Z9) || Z8))) || (Z3 && (Z9 && (Z6 || (Z1 || Z0))))) && (((Z3 && Z5) || +(Z4 || Z2)) && (Z5 && (Z6 || (Z0 || Z1))))) || Z1) || (Z4 || (Z1 || Z4)))) && Z9)))) +&& ((((Z6 || (((Z6 && (Z3 || Z9)) && Z6) && (Z1 && Z9))) && ((Z4 && (Z4 && Z3)) && +Z4)) && (((((Z1 && Z3) && (Z5 && Z2)) || (Z1 || (Z9 || Z1))) && (Z8 || Z1)) || ((Z4 || +Z5) && Z3))) && ((((((Z8 || Z4) || (Z6 && Z3)) || (Z4 || Z0)) || Z4) && (Z7 || Z5)) && +((Z8 || (Z2 && Z1)) && (Z8 || Z1)))))); + +constexpr int foo(X auto x) { return 1; } +constexpr int foo(Y auto y) { return 2; } +static_assert(foo(0) == 2); + +} + +}