[clang] [clang][DebugInfo] Cache repeated debug info lookups (PR #193785)

Clemens Wasser via cfe-commits cfe-commits at lists.llvm.org
Thu Apr 23 09:00:57 PDT 2026


https://github.com/clemenswasser created https://github.com/llvm/llvm-project/pull/193785

Found while profiling clang compiling SLPVectorizer.cpp.
Avoiding repeated debug location, type identifier, and file lookup work yields small compile-time wins for debug-info builds, mostly noise in optimized non-debug configs, but consistent improvements even up to -3.72% for debug-info builds:
https://llvm-compile-time-tracker.com/compare.php?from=78820cb91605693b7d768be4ebc8b66181d3e9c3&to=d41b2f249e68dad472cbad01435c96991113db7d&stat=instructions:u
Stats of RelWithDebInfo SLPVectorizer.cpp build before and after:
```text
Benchmark 1 (3 runs): main -O2 -g compiling SLPVectorizer.cpp with pch
  measurement          mean ± σ            min … max           outliers         delta
  wall_time          32.3s  ±  115ms    32.2s  … 32.4s           0 ( 0%)        0%
  peak_rss           1.07GB ±  567KB    1.07GB … 1.07GB          0 ( 0%)        0%
  cpu_cycles          159G  ±  488M      159G  …  160G           0 ( 0%)        0%
  instructions        214G  ± 50.5M      214G  …  214G           0 ( 0%)        0%
  cache_references   3.99G  ± 16.6M     3.98G  … 4.01G           0 ( 0%)        0%
  cache_misses        661M  ± 4.18M      658M  …  666M           0 ( 0%)        0%
  branch_misses      1.44G  ± 3.19M     1.43G  … 1.44G           0 ( 0%)        0%
Benchmark 2 (3 runs): perf/cgdebuginfo-lookup-cache -O2 -g compiling SLPVectorizer.cpp with pch
  measurement          mean ± σ            min … max           outliers         delta
  wall_time          32.2s  ±  139ms    32.1s  … 32.3s           0 ( 0%)          -  0.5% ±  0.9%
  peak_rss           1.07GB ±  453KB    1.07GB … 1.07GB          0 ( 0%)          -  0.2% ±  0.1%
  cpu_cycles          158G  ±  386M      158G  …  159G           0 ( 0%)          -  0.5% ±  0.6%
  instructions        211G  ± 87.2M      211G  …  211G           0 ( 0%)        ⚡-  1.4% ±  0.1%
  cache_references   3.98G  ± 9.84M     3.97G  … 3.99G           0 ( 0%)          -  0.3% ±  0.8%
  cache_misses        656M  ± 3.93M      652M  …  660M           0 ( 0%)          -  0.7% ±  1.4%
  branch_misses      1.44G  ± 2.29M     1.43G  … 1.44G           0 ( 0%)          +  0.0% ±  0.4%
```

>From d41b2f249e68dad472cbad01435c96991113db7d Mon Sep 17 00:00:00 2001
From: Clemens Wasser <clemens.wasser at gmail.com>
Date: Sat, 18 Apr 2026 12:56:58 +0200
Subject: [PATCH] [clang][DebugInfo] Cache repeated debug info lookups

---
 clang/lib/Basic/SourceManager.cpp |  19 +++--
 clang/lib/CodeGen/CGDebugInfo.cpp | 125 +++++++++++++++++++-----------
 clang/lib/CodeGen/CGDebugInfo.h   |  13 ++++
 3 files changed, 107 insertions(+), 50 deletions(-)

diff --git a/clang/lib/Basic/SourceManager.cpp b/clang/lib/Basic/SourceManager.cpp
index b6cc6ec9365f5..af89d1592d18c 100644
--- a/clang/lib/Basic/SourceManager.cpp
+++ b/clang/lib/Basic/SourceManager.cpp
@@ -812,7 +812,6 @@ FileID SourceManager::getFileIDLocal(SourceLocation::UIntTy SLocOffset) const {
   assert(SLocOffset >= LocalSLocEntryTable[0].getOffset() && SLocOffset > 0 &&
          "Invalid SLocOffset");
   assert(LocalSLocEntryTable.size() == LocalLocOffsetTable.size());
-  assert(LastFileIDLookup.ID >= 0 && "Only cache local file sloc entry");
 
   // After the first and second level caches, I see two common sorts of
   // behavior: 1) a lot of searched FileID's are "near" the cached file
@@ -833,10 +832,12 @@ FileID SourceManager::getFileIDLocal(SourceLocation::UIntTy SLocOffset) const {
   // upper bound of the search range.
   unsigned GreaterIndex = LocalLocOffsetTable.size();
   // Use the LastFileIDLookup to prune the search space.
-  if (LastLookupStartOffset < SLocOffset)
-    LessIndex = LastFileIDLookup.ID;
-  else
-    GreaterIndex = LastFileIDLookup.ID;
+  if (LastFileIDLookup.ID >= 0) {
+    if (LastLookupStartOffset < SLocOffset)
+      LessIndex = LastFileIDLookup.ID;
+    else
+      GreaterIndex = LastFileIDLookup.ID;
+  }
 
   // Find the FileID that contains this.
   unsigned NumProbes = 0;
@@ -887,7 +888,13 @@ FileID SourceManager::getFileIDLoaded(SourceLocation::UIntTy SLocOffset) const {
     return FileID();
   }
 
-  return FileID::get(ExternalSLocEntries->getSLocEntryID(SLocOffset));
+  FileID FID = FileID::get(ExternalSLocEntries->getSLocEntryID(SLocOffset));
+  LastFileIDLookup = FID;
+  LastLookupStartOffset = getSLocEntry(FID).getOffset();
+  FileID NextFID = getNextFileID(FID);
+  LastLookupEndOffset =
+      NextFID.isValid() ? getSLocEntry(NextFID).getOffset() : MaxLoadedOffset;
+  return FID;
 }
 
 SourceLocation SourceManager::
diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp
index c5a92a8e7ceb0..12119cc6678b1 100644
--- a/clang/lib/CodeGen/CGDebugInfo.cpp
+++ b/clang/lib/CodeGen/CGDebugInfo.cpp
@@ -471,58 +471,71 @@ StringRef CGDebugInfo::getSelectorName(Selector S) {
 
 StringRef CGDebugInfo::getClassName(const RecordDecl *RD,
                                     bool *NameIsSimplified) {
-  if (isa<ClassTemplateSpecializationDecl>(RD)) {
-    // Copy this name on the side and use its reference.
-    return internString(GetName(RD, false, NameIsSimplified));
-  }
-
   // quick optimization to avoid having to intern strings that are already
   // stored reliably elsewhere
   if (const IdentifierInfo *II = RD->getIdentifier())
     return II->getName();
 
-  // The CodeView printer in LLVM wants to see the names of unnamed types
-  // because they need to have a unique identifier.
-  // These names are used to reconstruct the fully qualified type names.
-  if (CGM.getCodeGenOpts().EmitCodeView) {
+  const auto *CanonicalRD = cast<RecordDecl>(RD->getCanonicalDecl());
+  if (auto It = ClassNameCache.find(CanonicalRD); It != ClassNameCache.end()) {
+    if (NameIsSimplified)
+      *NameIsSimplified = It->second.NameIsSimplified;
+    return It->second.Name;
+  }
+
+  StringRef Name;
+  bool Simplified = false;
+  if (isa<ClassTemplateSpecializationDecl>(RD)) {
+    // Copy this name on the side and use its reference.
+    Name = internString(GetName(RD, false, &Simplified));
+  } else if (CGM.getCodeGenOpts().EmitCodeView) {
+    // The CodeView printer in LLVM wants to see the names of unnamed types
+    // because they need to have a unique identifier.
+    // These names are used to reconstruct the fully qualified type names.
     if (const TypedefNameDecl *D = RD->getTypedefNameForAnonDecl()) {
       assert(RD->getDeclContext() == D->getDeclContext() &&
              "Typedef should not be in another decl context!");
       assert(D->getDeclName().getAsIdentifierInfo() &&
              "Typedef was not named!");
-      return D->getDeclName().getAsIdentifierInfo()->getName();
-    }
-
-    if (CGM.getLangOpts().CPlusPlus) {
-      StringRef Name;
+      Name = D->getDeclName().getAsIdentifierInfo()->getName();
+    } else if (CGM.getLangOpts().CPlusPlus) {
+      StringRef UnnamedName;
 
       ASTContext &Context = CGM.getContext();
       if (const DeclaratorDecl *DD = Context.getDeclaratorForUnnamedTagDecl(RD))
         // Anonymous types without a name for linkage purposes have their
         // declarator mangled in if they have one.
-        Name = DD->getName();
+        UnnamedName = DD->getName();
       else if (const TypedefNameDecl *TND =
                    Context.getTypedefNameForUnnamedTagDecl(RD))
         // Anonymous types without a name for linkage purposes have their
         // associate typedef mangled in if they have one.
-        Name = TND->getName();
+        UnnamedName = TND->getName();
 
       // Give lambdas a display name based on their name mangling.
-      if (const CXXRecordDecl *CXXRD = dyn_cast<CXXRecordDecl>(RD))
-        if (CXXRD->isLambda())
-          return internString(
+      if (const CXXRecordDecl *CXXRD = dyn_cast<CXXRecordDecl>(RD)) {
+        if (CXXRD->isLambda()) {
+          Name = internString(
               CGM.getCXXABI().getMangleContext().getLambdaString(CXXRD));
-
-      if (!Name.empty()) {
+        } else if (!UnnamedName.empty()) {
+          SmallString<256> UnnamedType("<unnamed-type-");
+          UnnamedType += UnnamedName;
+          UnnamedType += '>';
+          Name = internString(UnnamedType);
+        }
+      } else if (!UnnamedName.empty()) {
         SmallString<256> UnnamedType("<unnamed-type-");
-        UnnamedType += Name;
+        UnnamedType += UnnamedName;
         UnnamedType += '>';
-        return internString(UnnamedType);
+        Name = internString(UnnamedType);
       }
     }
   }
 
-  return StringRef();
+  ClassNameCache[CanonicalRD] = {Name, Simplified};
+  if (NameIsSimplified)
+    *NameIsSimplified = Simplified;
+  return Name;
 }
 
 std::optional<llvm::DIFile::ChecksumKind>
@@ -661,11 +674,30 @@ std::string CGDebugInfo::remapDIPath(StringRef Path) const {
   return P.str().str();
 }
 
+void CGDebugInfo::cacheLineAndColumn(SourceLocation Loc) const {
+  if (Loc == CachedLineColLoc)
+    return;
+
+  CachedLineColLoc = Loc;
+  CachedLine = 0;
+  CachedColumn = 0;
+  if (Loc.isInvalid())
+    return;
+
+  SourceManager &SM = CGM.getContext().getSourceManager();
+  PresumedLoc PLoc = SM.getPresumedLoc(Loc);
+  if (!PLoc.isValid())
+    return;
+
+  CachedLine = PLoc.getLine();
+  CachedColumn = PLoc.getColumn();
+}
+
 unsigned CGDebugInfo::getLineNumber(SourceLocation Loc) {
   if (Loc.isInvalid())
     return 0;
-  SourceManager &SM = CGM.getContext().getSourceManager();
-  return SM.getPresumedLoc(getMacroDebugLoc(CGM, Loc)).getLine();
+  cacheLineAndColumn(getMacroDebugLoc(CGM, Loc));
+  return CachedLine;
 }
 
 unsigned CGDebugInfo::getColumnNumber(SourceLocation Loc, bool Force) {
@@ -676,10 +708,8 @@ unsigned CGDebugInfo::getColumnNumber(SourceLocation Loc, bool Force) {
   // If the location is invalid then use the current column.
   if (Loc.isInvalid() && CurLoc.isInvalid())
     return 0;
-  SourceManager &SM = CGM.getContext().getSourceManager();
-  PresumedLoc PLoc =
-      SM.getPresumedLoc(Loc.isValid() ? getMacroDebugLoc(CGM, Loc) : CurLoc);
-  return PLoc.isValid() ? PLoc.getColumn() : 0;
+  cacheLineAndColumn(Loc.isValid() ? getMacroDebugLoc(CGM, Loc) : CurLoc);
+  return CachedColumn;
 }
 
 StringRef CGDebugInfo::getCurrentDirname() {
@@ -1413,23 +1443,29 @@ static bool needsTypeIdentifier(const TagDecl *TD, CodeGenModule &CGM,
 }
 
 // Returns a unique type identifier string if one exists, or an empty string.
-static SmallString<256> getTypeIdentifier(const TagType *Ty, CodeGenModule &CGM,
-                                          llvm::DICompileUnit *TheCU) {
-  SmallString<256> Identifier;
+StringRef CGDebugInfo::getTypeIdentifier(const TagType *Ty) {
   const TagDecl *TD = Ty->getDecl()->getDefinitionOrSelf();
+  const auto *CanonicalTD = cast<TagDecl>(TD->getCanonicalDecl());
+  if (auto It = TypeIdentifierCache.find(CanonicalTD);
+      It != TypeIdentifierCache.end())
+    return It->second;
 
+  StringRef Identifier;
   if (!needsTypeIdentifier(TD, CGM, TheCU))
-    return Identifier;
+    return TypeIdentifierCache[CanonicalTD] = Identifier;
   if (const auto *RD = dyn_cast<CXXRecordDecl>(TD))
     if (RD->getDefinition())
       if (RD->isDynamicClass() &&
           CGM.getVTableLinkage(RD) == llvm::GlobalValue::ExternalLinkage)
-        return Identifier;
+        return TypeIdentifierCache[CanonicalTD] = Identifier;
 
   // TODO: This is using the RTTI name. Is there a better way to get
   // a unique string for a type?
-  llvm::raw_svector_ostream Out(Identifier);
+  SmallString<256> IdentifierStorage;
+  llvm::raw_svector_ostream Out(IdentifierStorage);
   CGM.getCXXABI().getMangleContext().mangleCXXRTTIName(QualType(Ty, 0), Out);
+  Identifier = internString(IdentifierStorage);
+  TypeIdentifierCache[CanonicalTD] = Identifier;
   return Identifier;
 }
 
@@ -1478,10 +1514,10 @@ CGDebugInfo::getOrCreateRecordFwdDecl(const RecordType *Ty,
       Flags |= llvm::DINode::FlagNonTrivial;
 
   // Create the type.
-  SmallString<256> Identifier;
+  StringRef Identifier;
   // Don't include a linkage name in line tables only.
   if (CGM.getCodeGenOpts().hasReducedDebugInfo())
-    Identifier = getTypeIdentifier(Ty, CGM, TheCU);
+    Identifier = getTypeIdentifier(Ty);
   llvm::DICompositeType *RetTy = DBuilder.createReplaceableCompositeType(
       getTagForRecord(RD), RDName, Ctx, DefUnit, Line, 0, Size, Align, Flags,
       Identifier);
@@ -3936,8 +3972,7 @@ llvm::DIType *CGDebugInfo::CreateType(const HLSLInlineSpirvType *Ty,
   return nullptr;
 }
 
-static auto getEnumInfo(CodeGenModule &CGM, llvm::DICompileUnit *TheCU,
-                        const EnumType *Ty) {
+static auto getEnumInfo(CodeGenModule &CGM, const EnumType *Ty) {
   const EnumDecl *ED = Ty->getDecl()->getDefinitionOrSelf();
 
   uint64_t Size = 0;
@@ -3946,11 +3981,12 @@ static auto getEnumInfo(CodeGenModule &CGM, llvm::DICompileUnit *TheCU,
     Size = CGM.getContext().getTypeSize(QualType(Ty, 0));
     Align = getDeclAlignIfRequired(ED, CGM.getContext());
   }
-  return std::make_tuple(ED, Size, Align, getTypeIdentifier(Ty, CGM, TheCU));
+  return std::make_tuple(ED, Size, Align);
 }
 
 llvm::DIType *CGDebugInfo::CreateEnumType(const EnumType *Ty) {
-  auto [ED, Size, Align, Identifier] = getEnumInfo(CGM, TheCU, Ty);
+  auto [ED, Size, Align] = getEnumInfo(CGM, Ty);
+  StringRef Identifier = getTypeIdentifier(Ty);
 
   bool isImportedFromModule =
       DebugTypeExtRefs && ED->isFromASTFile() && ED->getDefinition();
@@ -3985,7 +4021,8 @@ llvm::DIType *CGDebugInfo::CreateEnumType(const EnumType *Ty) {
 }
 
 llvm::DIType *CGDebugInfo::CreateTypeDefinition(const EnumType *Ty) {
-  auto [ED, Size, Align, Identifier] = getEnumInfo(CGM, TheCU, Ty);
+  auto [ED, Size, Align] = getEnumInfo(CGM, Ty);
+  StringRef Identifier = getTypeIdentifier(Ty);
 
   SmallVector<llvm::Metadata *, 16> Enumerators;
   ED = ED->getDefinition();
@@ -4378,7 +4415,7 @@ llvm::DICompositeType *CGDebugInfo::CreateLimitedType(const RecordType *Ty) {
   // to be used.
   auto Align = getTypeAlignIfRequired(Ty, CGM.getContext());
 
-  SmallString<256> Identifier = getTypeIdentifier(Ty, CGM, TheCU);
+  StringRef Identifier = getTypeIdentifier(Ty);
 
   // Explicitly record the calling convention and export symbols for C++
   // records.
diff --git a/clang/lib/CodeGen/CGDebugInfo.h b/clang/lib/CodeGen/CGDebugInfo.h
index 8e1eda2d93ac0..88b0ed1e2d27f 100644
--- a/clang/lib/CodeGen/CGDebugInfo.h
+++ b/clang/lib/CodeGen/CGDebugInfo.h
@@ -69,6 +69,9 @@ class CGDebugInfo {
   ModuleMap *ClangModuleMap = nullptr;
   ASTSourceDescriptor PCHDescriptor;
   SourceLocation CurLoc;
+  mutable SourceLocation CachedLineColLoc;
+  mutable unsigned CachedLine = 0;
+  mutable unsigned CachedColumn = 0;
   llvm::MDNode *CurInlinedAt = nullptr;
   llvm::DIType *VTablePtrType = nullptr;
   llvm::DIType *ClassTy = nullptr;
@@ -165,6 +168,12 @@ class CGDebugInfo {
   /// using declarations and global alias variables) that aren't covered
   /// by other more specific caches.
   llvm::DenseMap<const Decl *, llvm::TrackingMDRef> DeclCache;
+  struct ClassNameCacheEntry {
+    StringRef Name;
+    bool NameIsSimplified;
+  };
+  llvm::DenseMap<const RecordDecl *, ClassNameCacheEntry> ClassNameCache;
+  llvm::DenseMap<const TagDecl *, StringRef> TypeIdentifierCache;
   llvm::DenseMap<const Decl *, llvm::TrackingMDRef> ImportedDeclCache;
   llvm::DenseMap<const NamespaceDecl *, llvm::TrackingMDRef> NamespaceCache;
   llvm::DenseMap<const NamespaceAliasDecl *, llvm::TrackingMDRef>
@@ -740,6 +749,8 @@ class CGDebugInfo {
   /// Return current directory name.
   StringRef getCurrentDirname();
 
+  void cacheLineAndColumn(SourceLocation Loc) const;
+
   /// Create new compile unit.
   void CreateCompileUnit();
 
@@ -857,6 +868,8 @@ class CGDebugInfo {
   StringRef getClassName(const RecordDecl *RD,
                          bool *NameIsSimplified = nullptr);
 
+  StringRef getTypeIdentifier(const TagType *Ty);
+
   /// Get the vtable name for the given class.
   StringRef getVTableName(const CXXRecordDecl *Decl);
 



More information about the cfe-commits mailing list