[clang] [Clang] Avoid quadratic pack-indexing instantiation (store only the selected element) (PR #213790)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Aug 3 15:50:38 PDT 2026
https://github.com/AnonMiraj created https://github.com/llvm/llvm-project/pull/213790
None
>From d89bdae8e715629b8199f400be0a46359ba26f1f Mon Sep 17 00:00:00 2001
From: Anonmiraj <ezzibrahimx at gmail.com>
Date: Tue, 4 Aug 2026 00:50:04 +0300
Subject: [PATCH] [Clang] Avoid quadratic pack-indexing instantiation (store
only the selected element)
---
clang/include/clang/AST/ExprCXX.h | 5 +-
clang/include/clang/AST/TypeBase.h | 4 +-
clang/lib/AST/ASTContext.cpp | 10 +++-
clang/lib/AST/ComputeDependence.cpp | 6 ++-
clang/lib/AST/ExprCXX.cpp | 14 +++--
clang/lib/Sema/TreeTransform.h | 84 +++++++++++++++++++++++++++++
6 files changed, 115 insertions(+), 8 deletions(-)
diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h
index d3d3b9c6d6326..c42c02e5edfcd 100644
--- a/clang/include/clang/AST/ExprCXX.h
+++ b/clang/include/clang/AST/ExprCXX.h
@@ -4638,7 +4638,10 @@ class PackIndexingExpr final
Expr *getSelectedExpr() const {
UnsignedOrNone Index = getSelectedIndex();
assert(Index && "extracting the indexed expression of a dependant pack");
- return getTrailingObjects()[*Index];
+ // Resolved nodes store only the selected expansion; unresolved nodes store
+ // the full list and are indexed by the evaluated index.
+ return getTrailingObjects()[
+ PackIndexingExprBits.TransformedExpressions == 1 ? 0 : *Index];
}
/// Return the trailing expressions, regardless of the expansion.
diff --git a/clang/include/clang/AST/TypeBase.h b/clang/include/clang/AST/TypeBase.h
index 530bfe72dac2b..c614fda53a636 100644
--- a/clang/include/clang/AST/TypeBase.h
+++ b/clang/include/clang/AST/TypeBase.h
@@ -6464,7 +6464,9 @@ class PackIndexingType final
QualType getSelectedType() const {
assert(hasSelectedType() && "Type is dependant");
- return *(getExpansionsPtr() + *getSelectedIndex());
+ // Resolved nodes store only the selected expansion; unresolved nodes store
+ // the full list and are indexed by the evaluated index.
+ return getExpansionsPtr()[Size == 1 ? 0 : *getSelectedIndex()];
}
UnsignedOrNone getSelectedIndex() const;
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 02a3f88431f58..3223ac0298838 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -6825,7 +6825,15 @@ QualType ASTContext::getPackIndexingType(QualType Pattern, Expr *IndexExpr,
UnsignedOrNone Index) const {
QualType Canonical;
if (FullySubstituted && Index) {
- Canonical = getCanonicalType(Expansions[*Index]);
+ unsigned SelIdx = Expansions.size() == 1 ? 0 : *Index;
+ assert(SelIdx < Expansions.size() && "pack index out of bounds");
+ QualType Selected = Expansions[SelIdx];
+ Canonical = getCanonicalType(Selected);
+ // Store only the selected element once resolved; keeping the whole
+ // expansion list would make each pack-indexing instantiation O(N).
+ if (!Selected->isInstantiationDependentType() &&
+ !IndexExpr->isInstantiationDependent())
+ Expansions = Expansions.slice(SelIdx, 1);
} else {
llvm::FoldingSetNodeID ID;
PackIndexingType::Profile(ID, *this, Pattern.getCanonicalType(), IndexExpr,
diff --git a/clang/lib/AST/ComputeDependence.cpp b/clang/lib/AST/ComputeDependence.cpp
index a819bb6dec599..657b83393f29c 100644
--- a/clang/lib/AST/ComputeDependence.cpp
+++ b/clang/lib/AST/ComputeDependence.cpp
@@ -398,8 +398,10 @@ ExprDependence clang::computeDependence(PackIndexingExpr *E) {
D |= PatternDep | ExprDependence::Instantiation;
else if (!E->getIndexExpr()->isInstantiationDependent()) {
UnsignedOrNone Index = E->getSelectedIndex();
- assert(Index && *Index < Exprs.size() && "pack index out of bound");
- D |= Exprs[*Index]->getDependence();
+ assert(Index && "pack index out of bound");
+ unsigned SelIdx = Exprs.size() == 1 ? 0 : *Index;
+ assert(SelIdx < Exprs.size() && "pack index out of bound");
+ D |= Exprs[SelIdx]->getDependence();
}
return D;
}
diff --git a/clang/lib/AST/ExprCXX.cpp b/clang/lib/AST/ExprCXX.cpp
index 6c1cde6540d85..ceaccfdb18fe2 100644
--- a/clang/lib/AST/ExprCXX.cpp
+++ b/clang/lib/AST/ExprCXX.cpp
@@ -1741,9 +1741,17 @@ PackIndexingExpr *PackIndexingExpr::Create(
Expr *PackIdExpr, Expr *IndexExpr, std::optional<int64_t> Index,
ArrayRef<Expr *> SubstitutedExprs, bool FullySubstituted) {
QualType Type;
- if (Index && FullySubstituted && !SubstitutedExprs.empty())
- Type = SubstitutedExprs[*Index]->getType();
- else
+ if (Index && FullySubstituted && !SubstitutedExprs.empty()) {
+ unsigned SelIdx = SubstitutedExprs.size() == 1 ? 0 : *Index;
+ assert(SelIdx < SubstitutedExprs.size() && "pack index out of bounds");
+ Expr *Selected = SubstitutedExprs[SelIdx];
+ Type = Selected->getType();
+ // Store only the selected element once resolved; keeping the whole
+ // expansion list would make pack-indexing instantiation O(N).
+ if (!Selected->isInstantiationDependent() &&
+ !IndexExpr->isInstantiationDependent())
+ SubstitutedExprs = SubstitutedExprs.slice(SelIdx, 1);
+ } else
Type = PackIdExpr->getType();
void *Storage =
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 2083fcd372e81..bf1cdaf5db9b7 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -7099,6 +7099,13 @@ template <typename Derived>
QualType
TreeTransform<Derived>::TransformPackIndexingType(TypeLocBuilder &TLB,
PackIndexingTypeLoc TL) {
+ // An already-resolved (non-dependent) pack-indexing type has nothing left to
+ // substitute.
+ if (!TL.getType()->isInstantiationDependentType()) {
+ TLB.pushFullCopy(TL);
+ return TL.getType();
+ }
+
// Transform the index
ExprResult IndexExpr;
{
@@ -7167,6 +7174,48 @@ TreeTransform<Derived>::TransformPackIndexingType(TypeLocBuilder &TLB,
SubtitutedTypes.push_back(Pack);
continue;
}
+ // Fast path: substitute only the selected element instead of all N. A
+ // pack-indexing type inside a pack expansion (`T...[Is]...`) is transformed
+ // once per outer element, so substituting the whole pack each time is
+ // O(N^2) in time and memory.
+ if (!RetainExpansion && Types.size() == 1 && IndexExpr.isUsable() &&
+ !IndexExpr.get()->isInstantiationDependent()) {
+ llvm::APSInt Value;
+ ExprResult CCE = SemaRef.CheckConvertedConstantExpression(
+ IndexExpr.get(), SemaRef.Context.getSizeType(), Value,
+ CCEKind::PackIndex);
+ if (!CCE.isUsable() || !Value.isRepresentableByInt64())
+ return QualType();
+ uint64_t V = Value.getZExtValue();
+ // NumExpansions is only a lower bound when the pack's argument still
+ // contains an unexpanded pack (GH116105), so V >= NumExpansions is not
+ // necessarily out of bounds: fall through to the full expansion, which
+ // lets Sema::BuildPackIndexingType diagnose a genuine OOB index.
+ if (V < *NumExpansions) {
+ QualType Selected;
+ {
+ Sema::ArgPackSubstIndexRAII SubstIndex(getSema(),
+ static_cast<unsigned>(V));
+ Selected = getDerived().TransformType(T);
+ }
+ if (Selected.isNull())
+ return QualType();
+ if (!Selected->containsUnexpandedParameterPack() &&
+ !Selected->isInstantiationDependentType()) {
+ Sema::ArgPackSubstIndexRAII SubstIndex(getSema(), std::nullopt);
+ QualType Result =
+ getDerived().TransformType(TLB, TL.getPatternLoc());
+ if (Result.isNull())
+ return QualType();
+ QualType Out = SemaRef.Context.getPackIndexingType(
+ Result, CCE.get(), /*FullySubstituted=*/true, {Selected},
+ /*Index=*/0u);
+ PackIndexingTypeLoc Loc = TLB.push<PackIndexingTypeLoc>(Out);
+ Loc.setEllipsisLoc(TL.getEllipsisLoc());
+ return Out;
+ }
+ }
+ }
for (unsigned I = 0; I != *NumExpansions; ++I) {
Sema::ArgPackSubstIndexRAII SubstIndex(getSema(), I);
QualType Out = getDerived().TransformType(T);
@@ -16913,6 +16962,41 @@ TreeTransform<Derived>::TransformPackIndexingExpr(PackIndexingExpr *E) {
E->getEllipsisLoc(), E->getRSquareLoc(), Pack.get(), IndexExpr.get(),
{}, /*FullySubstituted=*/false);
}
+ // Fast path: see TransformPackIndexingType.
+ if (!RetainExpansion && IndexExpr.isUsable() &&
+ !IndexExpr.get()->isInstantiationDependent()) {
+ llvm::APSInt Value(
+ SemaRef.Context.getIntWidth(SemaRef.Context.getSizeType()));
+ ExprResult CCE = SemaRef.CheckConvertedConstantExpression(
+ IndexExpr.get(), SemaRef.Context.getSizeType(), Value,
+ CCEKind::PackIndex);
+ if (!CCE.isUsable() || !Value.isRepresentableByInt64())
+ return ExprError();
+ uint64_t V = Value.getZExtValue();
+ // NumExpansions is only a lower bound when the pack's argument still
+ // contains an unexpanded pack (GH116105), so V >= NumExpansions is not
+ // necessarily out of bounds: fall through to the full expansion, which
+ // lets Sema::BuildPackIndexingExpr diagnose a genuine OOB index.
+ if (V < *NumExpansions) {
+ ExprResult Selected;
+ {
+ Sema::ArgPackSubstIndexRAII SubstIndex(getSema(),
+ static_cast<unsigned>(V));
+ Selected = getDerived().TransformExpr(Pattern);
+ }
+ if (Selected.isInvalid())
+ return ExprError();
+ if (!Selected.get()->containsUnexpandedParameterPack() &&
+ !Selected.get()->isInstantiationDependent()) {
+ Expr *SelectedExpr = Selected.get();
+ return PackIndexingExpr::Create(
+ getSema().getASTContext(), E->getEllipsisLoc(),
+ E->getRSquareLoc(), E->getPackIdExpression(), CCE.get(),
+ static_cast<int64_t>(V), SelectedExpr,
+ /*FullySubstituted=*/true);
+ }
+ }
+ }
for (unsigned I = 0; I != *NumExpansions; ++I) {
Sema::ArgPackSubstIndexRAII SubstIndex(getSema(), I);
ExprResult Out = getDerived().TransformExpr(Pattern);
More information about the cfe-commits
mailing list