[llvm] [Debugify] Simplify debugify for locations (PR #207374)

Stephen Tozer via llvm-commits llvm-commits at lists.llvm.org
Fri Jul 3 05:31:21 PDT 2026


https://github.com/SLTozer updated https://github.com/llvm/llvm-project/pull/207374

>From 98861f66751a49e1e1fbe96d9aa3f41e127535a2 Mon Sep 17 00:00:00 2001
From: Stephen Tozer <stephen.tozer at sony.com>
Date: Fri, 3 Jul 2026 12:07:01 +0100
Subject: [PATCH 1/2] [Debugify] Simplify debugify for locations

This patch attempts to improve the performance of debugify for locations,
by relying on coverage tracking to replace most of the functionality
provided via the DILocations map, in exchange for losing the ability to
distinguish between "dropped" and "not-generated" bugs, and requiring
coverage tracking to determine when new bugs appear in a pass instead of
reporting the same bug repeatedly across passes.

This patch is not without cost; the justifications for using it are that:
- Debugify locations is incredibly expensive; on a local build, without
  using any coverage-tracking, this patch takes the build time for sqlite3
  down from ~15 minutes to ~10 seconds.
- The difference between "dropped" and "not-generated" is a minor detail of
  a bug - besides helping to determine the cause of the bug, there's no
  fundamental difference in the correctness of either. Furthermore, almost
  no "dropped" bugs appear in the compiler anymore (since the debug location
  coverage tracker bot has been online).
- Requiring coverage tracking to prevent repeated bugs is a loss, but
  coverage tracking is not a very expensive feature to enable, and the only
  public automated tests that use debugify already enable coverage tracking.
---
 llvm/include/llvm/Transforms/Utils/Debugify.h |   2 -
 llvm/lib/Transforms/Utils/Debugify.cpp        | 153 +++++++-----------
 2 files changed, 56 insertions(+), 99 deletions(-)

diff --git a/llvm/include/llvm/Transforms/Utils/Debugify.h b/llvm/include/llvm/Transforms/Utils/Debugify.h
index 4e9c91e9844ba..c6b9c0c2352b7 100644
--- a/llvm/include/llvm/Transforms/Utils/Debugify.h
+++ b/llvm/include/llvm/Transforms/Utils/Debugify.h
@@ -37,8 +37,6 @@ using WeakInstValueMap =
 struct DebugInfoPerPass {
   // This maps a function name to its associated DISubprogram.
   DebugFnMap DIFunctions;
-  // This maps an instruction and the info about whether it has !dbg attached.
-  DebugInstMap DILocations;
   // This tracks value (instruction) deletion. If an instruction gets deleted,
   // WeakVH nulls itself.
   WeakInstValueMap InstToDelete;
diff --git a/llvm/lib/Transforms/Utils/Debugify.cpp b/llvm/lib/Transforms/Utils/Debugify.cpp
index e501b6f2a4e34..78ff2c1b35676 100644
--- a/llvm/lib/Transforms/Utils/Debugify.cpp
+++ b/llvm/lib/Transforms/Utils/Debugify.cpp
@@ -446,16 +446,13 @@ bool llvm::collectDebugInfoMetadata(Module &M,
         }
       }
     }
-
-    for (BasicBlock &BB : F) {
-      // Collect debug locations (!dbg) and debug variable intrinsics.
-      for (Instruction &I : BB) {
-        // Skip PHIs.
-        if (isa<PHINode>(I))
-          continue;
-
-        // Cllect dbg.values and dbg.declare.
-        if (DebugifyLevel > Level::Locations) {
+    if (DebugifyLevel > Level::Locations) {
+      for (BasicBlock &BB : F) {
+        // Collect debug variable records.
+        for (Instruction &I : BB) {
+          // PHIs have no variable records.
+          if (isa<PHINode>(I))
+            continue;
           auto HandleDbgVariable = [&](DbgVariableRecord *DbgVar) {
             if (!SP)
               return;
@@ -472,13 +469,6 @@ bool llvm::collectDebugInfoMetadata(Module &M,
           for (DbgVariableRecord &DVR : filterDbgVars(I.getDbgRecordRange()))
             HandleDbgVariable(&DVR);
         }
-
-        LLVM_DEBUG(dbgs() << "  Collecting info for inst: " << I << '\n');
-        DebugInfoBeforePass.InstToDelete.insert({&I, &I});
-
-        // Track the addresses to symbolize, if the feature is enabled.
-        collectStackAddresses(I);
-        DebugInfoBeforePass.DILocations.insert({&I, hasLoc(I)});
       }
     }
   }
@@ -527,73 +517,43 @@ static bool checkFunctions(const DebugFnMap &DIFunctionsBefore,
   return Preserved;
 }
 
-// This checks the preservation of the original debug info attached to
-// instructions.
-static bool checkInstructions(const DebugInstMap &DILocsBefore,
-                              const DebugInstMap &DILocsAfter,
-                              const WeakInstValueMap &InstToDelete,
-                              StringRef NameOfWrappedPass,
-                              StringRef FileNameFromCU,
-                              bool ShouldWriteIntoJSON,
-                              llvm::json::Array &Bugs) {
-  bool Preserved = true;
-  for (const auto &L : DILocsAfter) {
-    if (L.second)
-      continue;
-    auto Instr = L.first;
-
-    // In order to avoid pointer reuse/recycling, skip the values that might
-    // have been deleted during a pass.
-    auto WeakInstrPtr = InstToDelete.find(Instr);
-    if (WeakInstrPtr != InstToDelete.end() && !WeakInstrPtr->second)
-      continue;
-
-    auto FnName = Instr->getFunction()->getName();
-    auto BB = Instr->getParent();
-    auto BBName = BB->hasName() ? BB->getName() : "no-name";
-    auto InstName = Instruction::getOpcodeName(Instr->getOpcode());
-
-    auto CreateJSONBugEntry = [&](const char *Action) {
-      auto BugEntry = llvm::json::Object({
-          {"metadata", "DILocation"},
-          {"fn-name", FnName.str()},
-          {"bb-name", BBName.str()},
-          {"instr", InstName},
-          {"action", Action},
-      });
+static bool checkInstructionCoverage(Instruction &I,
+                                     StringRef NameOfWrappedPass,
+                                     StringRef FileNameFromCU,
+                                     bool ShouldWriteIntoJSON,
+                                     llvm::json::Array &Bugs) {
+  if (hasLoc(I))
+    return true;
+
+  Instruction *Instr = &I;
+  collectStackAddresses(I);
+  auto FnName = Instr->getFunction()->getName();
+  auto BB = Instr->getParent();
+  auto BBName = BB->hasName() ? BB->getName() : "no-name";
+  auto InstName = Instruction::getOpcodeName(Instr->getOpcode());
+
+  auto CreateJSONBugEntry = [&](const char *Action) {
+    auto BugEntry = llvm::json::Object({
+        {"metadata", "DILocation"},
+        {"fn-name", FnName.str()},
+        {"bb-name", BBName.str()},
+        {"instr", InstName},
+        {"action", Action},
+    });
 #if LLVM_ENABLE_DEBUGLOC_TRACKING_ORIGIN
-      if (!Instr->getDebugLoc().getOriginStackTraces().empty())
-        BugEntry.insert({"origin", symbolizeStackTrace(Instr)});
+    if (!Instr->getDebugLoc().getOriginStackTraces().empty())
+      BugEntry.insert({"origin", symbolizeStackTrace(Instr)});
 #endif
-      Bugs.push_back(std::move(BugEntry));
-    };
-
-    auto InstrIt = DILocsBefore.find(Instr);
-    if (InstrIt == DILocsBefore.end()) {
-      if (ShouldWriteIntoJSON)
-        CreateJSONBugEntry("not-generate");
-      else
-        dbg() << "WARNING: " << NameOfWrappedPass
-              << " did not generate DILocation for " << *Instr
-              << " (BB: " << BBName << ", Fn: " << FnName
-              << ", File: " << FileNameFromCU << ")\n";
-      Preserved = false;
-    } else {
-      if (!InstrIt->second)
-        continue;
-      // If the instr had the !dbg attached before the pass, consider it as
-      // a debug info issue.
-      if (ShouldWriteIntoJSON)
-        CreateJSONBugEntry("drop");
-      else
-        dbg() << "WARNING: " << NameOfWrappedPass << " dropped DILocation of "
-              << *Instr << " (BB: " << BBName << ", Fn: " << FnName
-              << ", File: " << FileNameFromCU << ")\n";
-      Preserved = false;
-    }
-  }
+    Bugs.push_back(std::move(BugEntry));
+  };
 
-  return Preserved;
+  if (ShouldWriteIntoJSON)
+    CreateJSONBugEntry("not-generate");
+  else
+    dbg() << "WARNING: " << NameOfWrappedPass
+          << " did not generate DILocation for " << *Instr << " (BB: " << BBName
+          << ", Fn: " << FnName << ", File: " << FileNameFromCU << ")\n";
+  return false;
 }
 
 // This checks the preservation of original debug variable intrinsics.
@@ -673,6 +633,16 @@ bool llvm::checkDebugInfoMetadata(Module &M,
   // Map the debug info holding DIs after a pass.
   DebugInfoPerPass DebugInfoAfterPass;
 
+  bool ShouldWriteIntoJSON = !OrigDIVerifyBugsReportFilePath.empty();
+
+  // TODO: The name of the module could be read better?
+  StringRef FileNameFromCU =
+      (cast<DICompileUnit>(M.getNamedMetadata("llvm.dbg.cu")->getOperand(0)))
+          ->getFilename();
+  llvm::json::Array Bugs;
+
+  bool ResultForInsts = true;
+
   // Visit each instruction.
   for (Function &F : Functions) {
     if (isFunctionSkipped(F))
@@ -724,37 +694,26 @@ bool llvm::checkDebugInfoMetadata(Module &M,
         LLVM_DEBUG(dbgs() << "  Collecting info for inst: " << I << '\n');
 
         // Track the addresses to symbolize, if the feature is enabled.
-        collectStackAddresses(I);
-        DebugInfoAfterPass.DILocations.insert({&I, hasLoc(I)});
+        bool InstResult = checkInstructionCoverage(
+            I, NameOfWrappedPass, FileNameFromCU, ShouldWriteIntoJSON, Bugs);
+        if (!InstResult)
+          I.setDebugLoc(DebugLoc::getUnknown());
+        ResultForInsts &= InstResult;
       }
     }
   }
 
-  // TODO: The name of the module could be read better?
-  StringRef FileNameFromCU =
-      (cast<DICompileUnit>(M.getNamedMetadata("llvm.dbg.cu")->getOperand(0)))
-          ->getFilename();
-
   auto DIFunctionsBefore = DebugInfoBeforePass.DIFunctions;
   auto DIFunctionsAfter = DebugInfoAfterPass.DIFunctions;
 
-  auto DILocsBefore = DebugInfoBeforePass.DILocations;
-  auto DILocsAfter = DebugInfoAfterPass.DILocations;
-
   auto InstToDelete = DebugInfoBeforePass.InstToDelete;
 
   auto DIVarsBefore = DebugInfoBeforePass.DIVariables;
   auto DIVarsAfter = DebugInfoAfterPass.DIVariables;
 
-  bool ShouldWriteIntoJSON = !OrigDIVerifyBugsReportFilePath.empty();
-  llvm::json::Array Bugs;
-
   bool ResultForFunc =
       checkFunctions(DIFunctionsBefore, DIFunctionsAfter, NameOfWrappedPass,
                      FileNameFromCU, ShouldWriteIntoJSON, Bugs);
-  bool ResultForInsts = checkInstructions(
-      DILocsBefore, DILocsAfter, InstToDelete, NameOfWrappedPass,
-      FileNameFromCU, ShouldWriteIntoJSON, Bugs);
 
 #if LLVM_ENABLE_DEBUGLOC_TRACKING_COVERAGE
   // If we are tracking DebugLoc coverage, replace each empty DebugLoc with an

>From 75c71a4f88f99ed6188aa1c32e5645bc7e406461 Mon Sep 17 00:00:00 2001
From: Stephen Tozer <stephen.tozer at sony.com>
Date: Fri, 3 Jul 2026 13:31:06 +0100
Subject: [PATCH 2/2] Update UnitTest warning string check

---
 llvm/unittests/Transforms/Utils/DebugifyTest.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/unittests/Transforms/Utils/DebugifyTest.cpp b/llvm/unittests/Transforms/Utils/DebugifyTest.cpp
index 1daf381ee2862..9cd3fe86be058 100644
--- a/llvm/unittests/Transforms/Utils/DebugifyTest.cpp
+++ b/llvm/unittests/Transforms/Utils/DebugifyTest.cpp
@@ -132,7 +132,7 @@ TEST(DebugInfoDrop, DropOriginalDebugInfo) {
   std::string StdOut = testing::internal::GetCapturedStderr();
 
   std::string ErrorForSP = "ERROR:  dropped DISubprogram of";
-  std::string WarningForLoc = "WARNING:  dropped DILocation of";
+  std::string WarningForLoc = "WARNING:  did not generate DILocation for";
   std::string FinalResult = "CheckModuleDebugify (original debuginfo): FAIL";
 
   EXPECT_TRUE(StdOut.find(ErrorForSP) != std::string::npos);



More information about the llvm-commits mailing list