https://github.com/lux-QAQ created 
https://github.com/llvm/llvm-project/pull/198168

This is related to #143129. The immediate leak reported there was addressed by
explicitly destroying the `ConstraintSatisfaction` stored in `CNSInfo`.

`DeductionFailureInfo` currently stores `ConstraintsNotSatisfied` diagnostic 
payloads as a `ConstraintSatisfaction` inside `CNSInfo`.

However, `CNSInfo` is allocated in `ASTContext` arena storage. `ASTContext` 
will reclaim the raw memory, but it will not automatically run C++ destructors 
for objects allocated into that arena. This is awkward for 
`ConstraintSatisfaction`, which owns `SmallVector` members and therefore may 
need destruction if those vectors grow out of their inline storage.

This patch changes `CNSInfo` to store a pointer to a const 
`ASTConstraintSatisfaction` instead. That type is already the flattened, 
AST-safe representation of constraint satisfaction data. As noted in 
`ASTConcept.h`, it is safe to store in AST storage, unlike 
`ConstraintSatisfaction`.

This avoids keeping a non-trivially-destructible `ConstraintSatisfaction` 
object inside `CNSInfo`, and reuses the existing 
`ASTConstraintSatisfaction::Create` logic, including the deep-copy handling for 
substitution diagnostics.

## Details

The `ConstraintsNotSatisfied` payload now stores a pointer instead of embedding 
a full `ConstraintSatisfaction` object:

```cpp
TemplateArgumentList *TemplateArgs;
const ASTConstraintSatisfaction *Satisfaction;

```

This patch also adds an overload to `Sema::DiagnoseUnsatisfiedConstraint` 
accepting `const ASTConstraintSatisfaction *`, so overload diagnostics can 
consume the flattened representation directly.

`static_assert(std::is_trivially_destructible_v<CNSInfo>)` was omitted to avoid 
adding an otherwise unnecessary `<type_traits>` include.

## Memory Impact

This change was motivated by the lifetime mismatch around `CNSInfo` and 
`ConstraintSatisfaction`, but it also reduces the size of each 
constraints-not-satisfied payload. In particular, `CNSInfo` no longer embeds 
the inline storage of the `SmallVector`s inside `ConstraintSatisfaction`.

On synthetic constrained-overload stress cases, peak RSS was lower than a 
baseline Release build:

| Case | Baseline | This patch | Change |
| --- | --- | --- | --- |
| `100x100` | 56,576 KB | 55,040 KB | -2.71% |
| `200x200` | 67,072 KB | 61,184 KB | -8.78% |
| `500x500` | 134,528 KB | 99,022 KB | -26.39% |
| `1000x500` | 215,900 KB | 145,324 KB | -32.69% |
| `1000x1000` | 372,572 KB | 231,660 KB | -37.82% |
| `many_200x200x8` | 67,968 KB | 62,464 KB | -8.10% |

Both the baseline and the patched compiler were built and measured directly on 
top of commit `bae540bde882aa1911ccffb0fe7691cb16831017`.


## RSS Stress Reproducer

The stress code generator used:

```python
#!/usr/bin/env python3
import argparse
import os

ap = argparse.ArgumentParser()
ap.add_argument("--candidates", type=int, required=True)
ap.add_argument("--calls", type=int, required=True)
ap.add_argument("-o", "--output", required=True)
args = ap.parse_args()

tmp = args.output + ".tmp"

with open(tmp, "w") as out:
    def p(s=""):
        print(s, file=out)

    p("#include <type_traits>")
    p("template<int I> struct Tag {};")
    p("")

    for i in range(args.candidates):
        p("template<class T>")
        p(f"concept Fail{i} = (sizeof(T) == {1000000 + i});")
        p("template<class T>")
        p(f"  requires Fail{i}<T>")
        p(f"void f(T, Tag<{i}>* = nullptr) {{}}")
        p("")

    p("void f(...) {}")
    p("")
    p("void test() {")
    for i in range(args.calls):
        p(f"  f({i});")
    p("}")
    p("int main() { test(); return 0; }")

os.replace(tmp, args.output)

```

Collected via:

```bash
BASELINE=/home/lux/code/build-baseline/bin/clang++
PR=/home/lux/code/build-pr/bin/clang++

$BASELINE -v
$PR -v

for pair in "100 100" "200 200" "500 500" "1000 500" "1000 1000"; do
  set -- $pair
  c=$1
  n=$2
  src="stress_${c}_${n}.cpp"

  ./gen_constraint_stress.py --candidates "$c" --calls "$n" -o "$src"

  for compiler_name in baseline pr; do
    if [ "$compiler_name" = baseline ]; then
      clang="$BASELINE"
    else
      clang="$PR"
    fi

    "$clang" -std=c++20 -fsyntax-only -Wno-everything "$src" >/dev/null

    for i in $(seq 1 5); do
      /usr/bin/time -f "rss_kb=%M elapsed_s=%e exit=%x" \
        "$clang" -std=c++20 -fsyntax-only -Wno-everything "$src" \
        >/dev/null
    done
  done
done

```

## Testing

```text
./build-pr/tools/clang/unittests/Sema/SemaTests
Passed: 67 tests.

```

```text
llvm-lit -sv -j20 build-pr/tools/clang/test --filter='Clang :: 
(Sema|SemaCXX|SemaTemplate)/|Clang :: CXX/(temp|over)/'
No unexpected failures.

```

```text
ninja -C /home/lux/code/build-pr -j30 check-clang
No unexpected failures.

```

`check-clang` results:

```text
-- Testing: 24679 tests, 32 workers --
Testing:  0.. 10.. 20.. 30.. 40.. 50.. 60.. 70.. 80.. 90..

Testing Time: 143.19s

Total Discovered Tests: 52090
  Skipped          :    10 (0.02%)
  Unsupported      :  5681 (10.91%)
  Passed           : 46374 (89.03%)
  Expectedly Failed:    25 (0.05%)

```

>From cfcb81097cd66428e42ef09edcc1eb2c91760933 Mon Sep 17 00:00:00 2001
From: lijinsong2025 <[email protected]>
Date: Sun, 17 May 2026 15:42:01 +0800
Subject: [PATCH] [Clang][Sema] Use ASTConstraintSatisfaction for
 constraints-not-satisfied deduction info

---
 clang/include/clang/Sema/Sema.h |  3 +++
 clang/lib/Sema/SemaConcept.cpp  | 14 ++++++++------
 clang/lib/Sema/SemaOverload.cpp | 17 ++++++++++-------
 3 files changed, 21 insertions(+), 13 deletions(-)

diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 5202244cee2a7..194884e9461fe 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -15075,6 +15075,9 @@ class Sema final : public SemaBase {
   void DiagnoseUnsatisfiedConstraint(const ConstraintSatisfaction 
&Satisfaction,
                                      SourceLocation Loc = {},
                                      bool First = true);
+  void
+  DiagnoseUnsatisfiedConstraint(const ASTConstraintSatisfaction &Satisfaction,
+                                SourceLocation Loc = {}, bool First = true);
 
   /// \brief Emit diagnostics explaining why a constraint expression was deemed
   /// unsatisfied.
diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp
index ac1c716b5c385..61f9048b678df 100644
--- a/clang/lib/Sema/SemaConcept.cpp
+++ b/clang/lib/Sema/SemaConcept.cpp
@@ -2007,16 +2007,18 @@ void Sema::DiagnoseUnsatisfiedConstraint(
 }
 
 void Sema::DiagnoseUnsatisfiedConstraint(
-    const ConceptSpecializationExpr *ConstraintExpr, bool First) {
-
-  const ASTConstraintSatisfaction &Satisfaction =
-      ConstraintExpr->getSatisfaction();
+    const ASTConstraintSatisfaction &Satisfaction, SourceLocation Loc,
+    bool First) {
 
   assert(!Satisfaction.IsSatisfied &&
          "Attempted to diagnose a satisfied constraint");
+  ::DiagnoseUnsatisfiedConstraint(*this, Satisfaction.records(), Loc, First);
+}
 
-  ::DiagnoseUnsatisfiedConstraint(*this, Satisfaction.records(),
-                                  ConstraintExpr->getBeginLoc(), First);
+void Sema::DiagnoseUnsatisfiedConstraint(
+    const ConceptSpecializationExpr *ConstraintExpr, bool First) {
+  DiagnoseUnsatisfiedConstraint(ConstraintExpr->getSatisfaction(),
+                                ConstraintExpr->getBeginLoc(), First);
 }
 
 namespace {
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index e11bbd7085798..af4d62e494c2e 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -11,6 +11,7 @@
 
//===----------------------------------------------------------------------===//
 
 #include "CheckExprLifetime.h"
+#include "clang/AST/ASTConcept.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/CXXInheritance.h"
 #include "clang/AST/Decl.h"
@@ -741,7 +742,7 @@ namespace {
   // unsatisfied constraints.
   struct CNSInfo {
     TemplateArgumentList *TemplateArgs;
-    ConstraintSatisfaction Satisfaction;
+    const ASTConstraintSatisfaction *Satisfaction;
   };
 }
 
@@ -822,9 +823,10 @@ clang::MakeDeductionFailureInfo(ASTContext &Context,
     break;
 
   case TemplateDeductionResult::ConstraintsNotSatisfied: {
-    CNSInfo *Saved = new (Context) CNSInfo;
-    Saved->TemplateArgs = Info.takeSugared();
-    Saved->Satisfaction = std::move(Info.AssociatedConstraintsSatisfaction);
+    auto *Saved = new (Context)
+        CNSInfo{Info.takeSugared(),
+                ASTConstraintSatisfaction::Create(
+                    Context, Info.AssociatedConstraintsSatisfaction)};
     Result.Data = Saved;
     break;
   }
@@ -872,7 +874,8 @@ void DeductionFailureInfo::Destroy() {
 
   case TemplateDeductionResult::ConstraintsNotSatisfied:
     // FIXME: Destroy the template argument list?
-    static_cast<CNSInfo *>(Data)->Satisfaction.~ConstraintSatisfaction();
+    // CNSInfo and ASTConstraintSatisfaction are ASTContext-allocated and do
+    // not own non-arena resources.
     Data = nullptr;
     if (PartialDiagnosticAt *Diag = getSFINAEDiagnostic()) {
       Diag->~PartialDiagnosticAt();
@@ -11386,7 +11389,7 @@ bool 
OverloadCandidate::NotValidBecauseConstraintExprHasError() const {
          static_cast<TemplateDeductionResult>(DeductionFailure.Result) ==
              TemplateDeductionResult::ConstraintsNotSatisfied &&
          static_cast<CNSInfo *>(DeductionFailure.Data)
-             ->Satisfaction.ContainsErrors;
+             ->Satisfaction->ContainsErrors;
 }
 
 void OverloadCandidateSet::AddDeferredTemplateCandidate(
@@ -12523,7 +12526,7 @@ static void DiagnoseBadDeduction(Sema &S, NamedDecl 
*Found, Decl *Templated,
         << TemplateArgString;
 
     S.DiagnoseUnsatisfiedConstraint(
-        static_cast<CNSInfo*>(DeductionFailure.Data)->Satisfaction);
+        *static_cast<CNSInfo *>(DeductionFailure.Data)->Satisfaction);
     return;
   }
   case TemplateDeductionResult::TooManyArguments:

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to