[clang] [Clang] Cache DeclContext-to-Decl conversions (NFC) (PR #223042)
Mehdi Amini via cfe-commits
cfe-commits at lists.llvm.org
Tue Sep 15 09:00:51 PDT 2026
https://github.com/joker-eph updated https://github.com/llvm/llvm-project/pull/223042
>From 2a109893ce3f5970a9c618b8845eb4d158b3c6e6 Mon Sep 17 00:00:00 2001
From: Mehdi Amini <joker.eph at gmail.com>
Date: Thu, 10 Sep 2026 05:32:04 -0700
Subject: [PATCH 1/2] Cache DeclContext-to-Decl conversions
Cache the corresponding Decl pointer after the first DeclContext
conversion and route parent traversal and generic casts through the
cache. Use relaxed atomic access so concurrent read-only AST traversal
remains race-free.
CTMark O0 (3 samples, CPU 6): 29.439800 s -> 29.303500 s (-0.463%).
Impact on significant TUs in MLIR build time:
- `mlir/lib/RegisterAllDialects.cpp`: 2.5437% fewer retired
instructions.
- `mlir/lib/Dialect/LLVMIR/IR/NVVMDialect.cpp`: 1.3250% fewer retired
instructions.
Assisted-by: Codex
---
clang/include/clang/AST/DeclBase.h | 30 ++++++++++++++++++++++++------
clang/lib/AST/DeclBase.cpp | 9 ++++++++-
2 files changed, 32 insertions(+), 7 deletions(-)
diff --git a/clang/include/clang/AST/DeclBase.h b/clang/include/clang/AST/DeclBase.h
index 9d233be282dbb..8d0fcd76dd5ca 100644
--- a/clang/include/clang/AST/DeclBase.h
+++ b/clang/include/clang/AST/DeclBase.h
@@ -33,6 +33,7 @@
#include "llvm/Support/PrettyStackTrace.h"
#include "llvm/Support/VersionTuple.h"
#include <algorithm>
+#include <atomic>
#include <cassert>
#include <cstddef>
#include <iterator>
@@ -1464,6 +1465,8 @@ enum class LinkageSpecLanguageIDs;
/// BlockDecl
/// CapturedDecl
class DeclContext {
+ friend class Decl;
+
/// For makeDeclVisibleInContextImpl
friend class ASTDeclReader;
/// For checking the new bits in the Serialization part.
@@ -2101,6 +2104,12 @@ class DeclContext {
/// another pointer.
mutable Decl *LastDecl = nullptr;
+ /// The corresponding declaration, cached after the first conversion.
+ /// This correspondence is immutable. Relaxed atomics allow concurrent
+ /// read-only AST traversals to populate the cache without synchronizing
+ /// mutations to the AST itself.
+ mutable std::atomic<Decl *> CachedDecl = nullptr;
+
/// Build up a chain of declarations.
///
/// \returns the first/last pair of declarations.
@@ -2122,10 +2131,19 @@ class DeclContext {
const char *getDeclKindName() const;
- /// getParent - Returns the containing DeclContext.
- DeclContext *getParent() {
- return cast<Decl>(this)->getDeclContext();
+ /// Return the declaration containing this context.
+ Decl *getAsDecl() {
+ if (Decl *Cached = CachedDecl.load(std::memory_order_relaxed))
+ return Cached;
+ return Decl::castFromDeclContext(this);
}
+
+ const Decl *getAsDecl() const {
+ return const_cast<DeclContext *>(this)->getAsDecl();
+ }
+
+ /// getParent - Returns the containing DeclContext.
+ DeclContext *getParent() { return getAsDecl()->getDeclContext(); }
const DeclContext *getParent() const {
return const_cast<DeclContext*>(this)->getParent();
}
@@ -2140,7 +2158,7 @@ class DeclContext {
/// // getLexicalParent() == translation unit
///
DeclContext *getLexicalParent() {
- return cast<Decl>(this)->getLexicalDeclContext();
+ return getAsDecl()->getLexicalDeclContext();
}
const DeclContext *getLexicalParent() const {
return const_cast<DeclContext*>(this)->getLexicalParent();
@@ -2830,11 +2848,11 @@ template <class ToTy,
bool IsKnownSubtype = ::std::is_base_of<DeclContext, ToTy>::value>
struct cast_convert_decl_context {
static const ToTy *doit(const DeclContext *Val) {
- return static_cast<const ToTy*>(Decl::castFromDeclContext(Val));
+ return static_cast<const ToTy *>(Val->getAsDecl());
}
static ToTy *doit(DeclContext *Val) {
- return static_cast<ToTy*>(Decl::castFromDeclContext(Val));
+ return static_cast<ToTy *>(Val->getAsDecl());
}
};
diff --git a/clang/lib/AST/DeclBase.cpp b/clang/lib/AST/DeclBase.cpp
index 70f61fa57a682..e0903a19a8c5a 100644
--- a/clang/lib/AST/DeclBase.cpp
+++ b/clang/lib/AST/DeclBase.cpp
@@ -1080,16 +1080,23 @@ const AttrVec &Decl::getAttrs() const {
}
Decl *Decl::castFromDeclContext (const DeclContext *D) {
+ if (Decl *Cached = D->CachedDecl.load(std::memory_order_relaxed))
+ return Cached;
+
Decl::Kind DK = D->getDeclKind();
+ Decl *Result = nullptr;
switch (DK) {
#define DECL(NAME, BASE)
#define DECL_CONTEXT(NAME) \
case Decl::NAME: \
- return static_cast<NAME##Decl *>(const_cast<DeclContext *>(D));
+ Result = static_cast<NAME##Decl *>(const_cast<DeclContext *>(D)); \
+ break;
#include "clang/AST/DeclNodes.inc"
default:
llvm_unreachable("a decl that inherits DeclContext isn't handled");
}
+ D->CachedDecl.store(Result, std::memory_order_relaxed);
+ return Result;
}
DeclContext *Decl::castToDeclContext(const Decl *D) {
>From c5eae44e7ad12116d9bc1f62406240712cf24678 Mon Sep 17 00:00:00 2001
From: Mehdi Amini <joker.eph at gmail.com>
Date: Thu, 10 Sep 2026 05:32:04 -0700
Subject: [PATCH 2/2] Cache DeclContext-to-Decl conversions
Store the owning Decl pointer when constructing each DeclContext and route
parent traversal and generic casts through it. This avoids a lazy lookup and
does not imply support for concurrent AST traversal.
CTMark O0 (three alternating matched-build samples, CPU 6): 28.990700 s ->
28.702600 s (-0.9938%). Peak build RSS: 258016 -> 259276 KiB (+0.4883%).
All 632 normalized objects matched in each pair.
MLIR build-time medians (three alternating matched-build samples, CPU 6):
- `mlir/lib/RegisterAllDialects.cpp`: 2.8115% fewer retired instructions,
0.7353% less user CPU, 0.1972% less wall time, and 7144 KiB (+0.5257%)
peak RSS.
- `mlir/lib/Dialect/LLVMIR/IR/NVVMDialect.cpp`: 1.5841% fewer retired
instructions, 1.1120% less user CPU, 0.9893% less wall time, and 912 KiB
(+0.0795%) peak RSS.
All MLIR outputs matched after removing only `.comment`; each concurrency
guard log was empty.
Assisted-by: Codex
---
clang/include/clang/AST/Decl.h | 9 +++++----
clang/include/clang/AST/DeclBase.h | 19 +++++--------------
clang/include/clang/AST/DeclCXX.h | 3 ++-
clang/include/clang/AST/DeclOpenMP.h | 2 +-
clang/lib/AST/Decl.cpp | 18 ++++++++++--------
clang/lib/AST/DeclBase.cpp | 22 +++-------------------
clang/lib/AST/DeclCXX.cpp | 4 ++--
clang/lib/AST/DeclObjC.cpp | 6 +++---
clang/lib/AST/DeclOpenMP.cpp | 2 +-
clang/lib/AST/DeclTemplate.cpp | 2 +-
10 files changed, 33 insertions(+), 54 deletions(-)
diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index eafeeecac7794..dba6f60397bba 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -247,8 +247,8 @@ class PragmaDetectMismatchDecl final
/// lookup in the extern "C" context.
class ExternCContextDecl : public Decl, public DeclContext {
explicit ExternCContextDecl(TranslationUnitDecl *TU)
- : Decl(ExternCContext, TU, SourceLocation()),
- DeclContext(ExternCContext) {}
+ : Decl(ExternCContext, TU, SourceLocation()),
+ DeclContext(ExternCContext, this) {}
virtual void anchor();
@@ -4775,7 +4775,8 @@ class TopLevelStmtDecl : public Decl, public DeclContext {
bool IsSemiMissing = false;
TopLevelStmtDecl(DeclContext *DC, SourceLocation L, Stmt *S)
- : Decl(TopLevelStmt, DC, L), DeclContext(TopLevelStmt), Statement(S) {}
+ : Decl(TopLevelStmt, DC, L), DeclContext(TopLevelStmt, this),
+ Statement(S) {}
virtual void anchor();
@@ -5274,7 +5275,7 @@ class ExportDecl final : public Decl, public DeclContext {
SourceLocation RBraceLoc;
ExportDecl(DeclContext *DC, SourceLocation ExportLoc)
- : Decl(Export, DC, ExportLoc), DeclContext(Export),
+ : Decl(Export, DC, ExportLoc), DeclContext(Export, this),
RBraceLoc(SourceLocation()) {}
public:
diff --git a/clang/include/clang/AST/DeclBase.h b/clang/include/clang/AST/DeclBase.h
index 8d0fcd76dd5ca..d40fe642a4a2d 100644
--- a/clang/include/clang/AST/DeclBase.h
+++ b/clang/include/clang/AST/DeclBase.h
@@ -2104,11 +2104,8 @@ class DeclContext {
/// another pointer.
mutable Decl *LastDecl = nullptr;
- /// The corresponding declaration, cached after the first conversion.
- /// This correspondence is immutable. Relaxed atomics allow concurrent
- /// read-only AST traversals to populate the cache without synchronizing
- /// mutations to the AST itself.
- mutable std::atomic<Decl *> CachedDecl = nullptr;
+ /// The declaration corresponding to this context.
+ Decl *const CorrespondingDecl;
/// Build up a chain of declarations.
///
@@ -2116,7 +2113,7 @@ class DeclContext {
static std::pair<Decl *, Decl *>
BuildDeclChain(ArrayRef<Decl*> Decls, bool FieldsAlreadyLoaded);
- DeclContext(Decl::Kind K);
+ DeclContext(Decl::Kind K, Decl *D);
public:
~DeclContext();
@@ -2132,15 +2129,9 @@ class DeclContext {
const char *getDeclKindName() const;
/// Return the declaration containing this context.
- Decl *getAsDecl() {
- if (Decl *Cached = CachedDecl.load(std::memory_order_relaxed))
- return Cached;
- return Decl::castFromDeclContext(this);
- }
+ Decl *getAsDecl() { return CorrespondingDecl; }
- const Decl *getAsDecl() const {
- return const_cast<DeclContext *>(this)->getAsDecl();
- }
+ const Decl *getAsDecl() const { return CorrespondingDecl; }
/// getParent - Returns the containing DeclContext.
DeclContext *getParent() { return getAsDecl()->getDeclContext(); }
diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index afe46fae1bceb..0c6549cc6ef90 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -2117,7 +2117,8 @@ class CXXDeductionGuideDecl : public FunctionDecl {
/// template argument list imposed by the compound requirement.
class RequiresExprBodyDecl : public Decl, public DeclContext {
RequiresExprBodyDecl(ASTContext &C, DeclContext *DC, SourceLocation StartLoc)
- : Decl(RequiresExprBody, DC, StartLoc), DeclContext(RequiresExprBody) {}
+ : Decl(RequiresExprBody, DC, StartLoc),
+ DeclContext(RequiresExprBody, this) {}
public:
friend class ASTDeclReader;
diff --git a/clang/include/clang/AST/DeclOpenMP.h b/clang/include/clang/AST/DeclOpenMP.h
index 06414cef6baf3..c76f590fec2c1 100644
--- a/clang/include/clang/AST/DeclOpenMP.h
+++ b/clang/include/clang/AST/DeclOpenMP.h
@@ -365,7 +365,7 @@ class OMPDeclareMapperDecl final : public OMPDeclarativeDirective<ValueDecl>,
QualType Ty, DeclarationName VarName,
OMPDeclareMapperDecl *PrevDeclInScope)
: OMPDeclarativeDirective<ValueDecl>(OMPDeclareMapper, DC, L, Name, Ty),
- DeclContext(OMPDeclareMapper), VarName(VarName),
+ DeclContext(OMPDeclareMapper, this), VarName(VarName),
PrevDeclInScope(PrevDeclInScope) {}
void setPrevDeclInScope(OMPDeclareMapperDecl *Prev) {
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index d1d296dd60d14..f3443479a8f9a 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -103,7 +103,7 @@ bool Decl::isOutOfLine() const {
TranslationUnitDecl::TranslationUnitDecl(ASTContext &ctx)
: Decl(TranslationUnit, nullptr, SourceLocation()),
- DeclContext(TranslationUnit), redeclarable_base(ctx), Ctx(ctx) {}
+ DeclContext(TranslationUnit, this), redeclarable_base(ctx), Ctx(ctx) {}
//===----------------------------------------------------------------------===//
// NamedDecl Implementation
@@ -3073,7 +3073,7 @@ FunctionDecl::FunctionDecl(Kind DK, ASTContext &C, DeclContext *DC,
const AssociatedConstraint &TrailingRequiresClause)
: DeclaratorDecl(DK, DC, NameInfo.getLoc(), NameInfo.getName(), T, TInfo,
StartLoc),
- DeclContext(DK), redeclarable_base(C), Body(), ODRHash(0),
+ DeclContext(DK, this), redeclarable_base(C), Body(), ODRHash(0),
EndRangeLoc(NameInfo.getEndLoc()), DNLoc(NameInfo.getInfo()) {
assert(T.isNull() || T->isFunctionType());
FunctionDeclBits.SClass = S;
@@ -4939,7 +4939,8 @@ const FieldDecl *FieldDecl::findCountedByField() const {
TagDecl::TagDecl(Kind DK, TagKind TK, const ASTContext &C, DeclContext *DC,
SourceLocation L, IdentifierInfo *Id, TagDecl *PrevDecl,
SourceLocation StartL)
- : TypeDecl(DK, DC, L, Id, StartL), DeclContext(DK), redeclarable_base(C),
+ : TypeDecl(DK, DC, L, Id, StartL), DeclContext(DK, this),
+ redeclarable_base(C),
TypedefNameDeclOrQualifier((TypedefNameDecl *)nullptr) {
assert((DK != Enum || TK == TagTypeKind::Enum) &&
"EnumDecl not matched with TagTypeKind::Enum");
@@ -5501,7 +5502,7 @@ unsigned RecordDecl::getODRHash() {
//===----------------------------------------------------------------------===//
BlockDecl::BlockDecl(DeclContext *DC, SourceLocation CaretLoc)
- : Decl(Block, DC, CaretLoc), DeclContext(Block) {
+ : Decl(Block, DC, CaretLoc), DeclContext(Block, this) {
setIsVariadic(false);
setCapturesCXXThis(false);
setBlockMissingReturnType(true);
@@ -5724,7 +5725,7 @@ BlockDecl *BlockDecl::CreateDeserialized(ASTContext &C, GlobalDeclID ID) {
OutlinedFunctionDecl::OutlinedFunctionDecl(DeclContext *DC, unsigned NumParams)
: Decl(OutlinedFunction, DC, SourceLocation()),
- DeclContext(OutlinedFunction), NumParams(NumParams),
+ DeclContext(OutlinedFunction, this), NumParams(NumParams),
BodyAndNothrow(nullptr, false) {}
OutlinedFunctionDecl *OutlinedFunctionDecl::Create(ASTContext &C,
@@ -5752,7 +5753,7 @@ void OutlinedFunctionDecl::setNothrow(bool Nothrow) {
}
CapturedDecl::CapturedDecl(DeclContext *DC, unsigned NumParams)
- : Decl(Captured, DC, SourceLocation()), DeclContext(Captured),
+ : Decl(Captured, DC, SourceLocation()), DeclContext(Captured, this),
NumParams(NumParams), ContextParam(0), BodyAndNothrow(nullptr, false) {}
CapturedDecl *CapturedDecl::Create(ASTContext &C, DeclContext *DC,
@@ -5971,8 +5972,9 @@ HLSLBufferDecl::HLSLBufferDecl(DeclContext *DC, bool CBuffer,
SourceLocation KwLoc, IdentifierInfo *ID,
SourceLocation IDLoc, SourceLocation LBrace)
: NamedDecl(Decl::Kind::HLSLBuffer, DC, IDLoc, DeclarationName(ID)),
- DeclContext(Decl::Kind::HLSLBuffer), LBraceLoc(LBrace), KwLoc(KwLoc),
- IsCBuffer(CBuffer), HasValidPackoffset(false), LayoutStruct(nullptr) {}
+ DeclContext(Decl::Kind::HLSLBuffer, this), LBraceLoc(LBrace),
+ KwLoc(KwLoc), IsCBuffer(CBuffer), HasValidPackoffset(false),
+ LayoutStruct(nullptr) {}
HLSLBufferDecl *HLSLBufferDecl::Create(ASTContext &C,
DeclContext *LexicalParent, bool CBuffer,
diff --git a/clang/lib/AST/DeclBase.cpp b/clang/lib/AST/DeclBase.cpp
index e0903a19a8c5a..3d4be8154d447 100644
--- a/clang/lib/AST/DeclBase.cpp
+++ b/clang/lib/AST/DeclBase.cpp
@@ -1079,24 +1079,8 @@ const AttrVec &Decl::getAttrs() const {
return getASTContext().getDeclAttrs(this);
}
-Decl *Decl::castFromDeclContext (const DeclContext *D) {
- if (Decl *Cached = D->CachedDecl.load(std::memory_order_relaxed))
- return Cached;
-
- Decl::Kind DK = D->getDeclKind();
- Decl *Result = nullptr;
- switch (DK) {
-#define DECL(NAME, BASE)
-#define DECL_CONTEXT(NAME) \
- case Decl::NAME: \
- Result = static_cast<NAME##Decl *>(const_cast<DeclContext *>(D)); \
- break;
-#include "clang/AST/DeclNodes.inc"
- default:
- llvm_unreachable("a decl that inherits DeclContext isn't handled");
- }
- D->CachedDecl.store(Result, std::memory_order_relaxed);
- return Result;
+Decl *Decl::castFromDeclContext(const DeclContext *D) {
+ return const_cast<Decl *>(D->getAsDecl());
}
DeclContext *Decl::castToDeclContext(const Decl *D) {
@@ -1315,7 +1299,7 @@ Decl *DeclContext::getNonClosureAncestor() {
// DeclContext Implementation
//===----------------------------------------------------------------------===//
-DeclContext::DeclContext(Decl::Kind K) {
+DeclContext::DeclContext(Decl::Kind K, Decl *D) : CorrespondingDecl(D) {
DeclContextBits.DeclKind = K;
setHasExternalLexicalStorage(false);
setHasExternalVisibleStorage(false);
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index f0da56542ae7e..0b46ab1662d5d 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -3302,7 +3302,7 @@ bool CXXConversionDecl::isLambdaToBlockPointerConversion() const {
LinkageSpecDecl::LinkageSpecDecl(DeclContext *DC, SourceLocation ExternLoc,
SourceLocation LangLoc,
LinkageSpecLanguageIDs lang, bool HasBraces)
- : Decl(LinkageSpec, DC, LangLoc), DeclContext(LinkageSpec),
+ : Decl(LinkageSpec, DC, LangLoc), DeclContext(LinkageSpec, this),
ExternLoc(ExternLoc), RBraceLoc(SourceLocation()) {
setLanguage(lang);
LinkageSpecDeclBits.HasBraces = HasBraces;
@@ -3364,7 +3364,7 @@ NamespaceDecl::NamespaceDecl(ASTContext &C, DeclContext *DC, bool Inline,
SourceLocation StartLoc, SourceLocation IdLoc,
IdentifierInfo *Id, NamespaceDecl *PrevDecl,
bool Nested)
- : NamespaceBaseDecl(Namespace, DC, IdLoc, Id), DeclContext(Namespace),
+ : NamespaceBaseDecl(Namespace, DC, IdLoc, Id), DeclContext(Namespace, this),
redeclarable_base(C), LocStart(StartLoc) {
setInline(Inline);
setNested(Nested);
diff --git a/clang/lib/AST/DeclObjC.cpp b/clang/lib/AST/DeclObjC.cpp
index 1adf6c9e048a9..06d02c64a14f2 100644
--- a/clang/lib/AST/DeclObjC.cpp
+++ b/clang/lib/AST/DeclObjC.cpp
@@ -66,7 +66,7 @@ ObjCContainerDecl::ObjCContainerDecl(Kind DK, DeclContext *DC,
const IdentifierInfo *Id,
SourceLocation nameLoc,
SourceLocation atStartLoc)
- : NamedDecl(DK, DC, nameLoc, Id), DeclContext(DK) {
+ : NamedDecl(DK, DC, nameLoc, Id), DeclContext(DK, this) {
setAtStartLoc(atStartLoc);
}
@@ -823,8 +823,8 @@ ObjCMethodDecl::ObjCMethodDecl(
bool isSynthesizedAccessorStub, bool isImplicitlyDeclared, bool isDefined,
ObjCImplementationControl impControl, bool HasRelatedResultType)
: NamedDecl(ObjCMethod, contextDecl, beginLoc, SelInfo),
- DeclContext(ObjCMethod), MethodDeclType(T), ReturnTInfo(ReturnTInfo),
- DeclEndLoc(endLoc) {
+ DeclContext(ObjCMethod, this), MethodDeclType(T),
+ ReturnTInfo(ReturnTInfo), DeclEndLoc(endLoc) {
// Initialized the bits stored in DeclContext.
ObjCMethodDeclBits.Family =
diff --git a/clang/lib/AST/DeclOpenMP.cpp b/clang/lib/AST/DeclOpenMP.cpp
index ef08a1c30042f..0a59b070023fe 100644
--- a/clang/lib/AST/DeclOpenMP.cpp
+++ b/clang/lib/AST/DeclOpenMP.cpp
@@ -132,7 +132,7 @@ OMPRequiresDecl *OMPRequiresDecl::CreateDeserialized(ASTContext &C,
OMPDeclareReductionDecl::OMPDeclareReductionDecl(
Kind DK, DeclContext *DC, SourceLocation L, DeclarationName Name,
QualType Ty, OMPDeclareReductionDecl *PrevDeclInScope)
- : ValueDecl(DK, DC, L, Name, Ty), DeclContext(DK), Combiner(nullptr),
+ : ValueDecl(DK, DC, L, Name, Ty), DeclContext(DK, this), Combiner(nullptr),
PrevDeclInScope(PrevDeclInScope) {
setInitializer(nullptr, OMPDeclareReductionInitKind::Call);
}
diff --git a/clang/lib/AST/DeclTemplate.cpp b/clang/lib/AST/DeclTemplate.cpp
index 64cb49b0aa53e..66ab9885f2907 100644
--- a/clang/lib/AST/DeclTemplate.cpp
+++ b/clang/lib/AST/DeclTemplate.cpp
@@ -1955,7 +1955,7 @@ SourceRange ExplicitInstantiationDecl::getSourceRange() const {
CXXExpansionStmtDecl::CXXExpansionStmtDecl(DeclContext *DC, SourceLocation Loc,
NonTypeTemplateParmDecl *NTTP)
- : Decl(CXXExpansionStmt, DC, Loc), DeclContext(CXXExpansionStmt),
+ : Decl(CXXExpansionStmt, DC, Loc), DeclContext(CXXExpansionStmt, this),
IndexNTTP(NTTP) {}
CXXExpansionStmtDecl *
More information about the cfe-commits
mailing list