[llvm] [llvm-dwarfdump][LineCov 3/3] Add IR analysis for variable coverage (PR #195342)

via llvm-commits llvm-commits at lists.llvm.org
Mon Jun 1 04:15:48 PDT 2026


https://github.com/unexpectedlydefined updated https://github.com/llvm/llvm-project/pull/195342

>From 5f8cc2cf444f0ae27eadaf40287459e6795a5d49 Mon Sep 17 00:00:00 2001
From: unexpectedlydefined <unexpectedlydefined at gmail.com>
Date: Tue, 4 Nov 2025 13:22:35 +0000
Subject: [PATCH 1/2] Add IR analysis for variable coverage

---
 llvm/docs/CommandGuide/llvm-dwarfdump.rst     |   5 +
 .../tools/llvm-dwarfdump/X86/coverage.test    |  24 +++
 llvm/tools/llvm-dwarfdump/Coverage.cpp        | 186 ++++++++++++++++--
 llvm/tools/llvm-dwarfdump/llvm-dwarfdump.cpp  |  11 +-
 llvm/tools/llvm-dwarfdump/llvm-dwarfdump.h    |   4 +-
 5 files changed, 209 insertions(+), 21 deletions(-)

diff --git a/llvm/docs/CommandGuide/llvm-dwarfdump.rst b/llvm/docs/CommandGuide/llvm-dwarfdump.rst
index 03d3623259610..ecf59d335be8c 100644
--- a/llvm/docs/CommandGuide/llvm-dwarfdump.rst
+++ b/llvm/docs/CommandGuide/llvm-dwarfdump.rst
@@ -203,6 +203,11 @@ OPTIONS
             File to use as the baseline for variable coverage statistics
             (implies :option:`--show-variable-coverage`)
 
+.. option:: --variable-coverage-bitcode-file
+
+            File containing bitcode used for calculating variable definedness
+            in coverage statistics (implies :option:`--show-variable-coverage`)
+
 .. option:: --combine-inline-variable-instances
 
             Use with :option:`--show-variable-coverage` to average variable
diff --git a/llvm/test/tools/llvm-dwarfdump/X86/coverage.test b/llvm/test/tools/llvm-dwarfdump/X86/coverage.test
index 647f277ae64e5..5539ef93c9999 100644
--- a/llvm/test/tools/llvm-dwarfdump/X86/coverage.test
+++ b/llvm/test/tools/llvm-dwarfdump/X86/coverage.test
@@ -36,3 +36,27 @@ BASELINE-NEXT: fn1 u test.c:4 1 14 0.0714 7 0.5
 BASELINE-NEXT: fn1 v test.c:13 0 14 0 7 0.5
 BASELINE-NEXT: fn1 x test.c:3 7 14 0.5 7 0.5
 BASELINE-NEXT: fn1 y test.c:3 7 14 0.5 7 0.5
+
+RUN: llvm-dwarfdump --show-variable-coverage --variable-coverage-bitcode-file=%S/Inputs/coverage.ll %t.o | FileCheck %s --check-prefix=BITCODE
+
+BITCODE:      Variable coverage statistics:
+BITCODE-NEXT: Function InlChain Variable Decl LinesCovered
+BITCODE-NEXT: f k test.c:20 2
+BITCODE-NEXT: f l test.c:20 3
+BITCODE-NEXT: fn1 a test.c:11 6
+BITCODE-NEXT: fn1 u test.c:4 12
+BITCODE-NEXT: fn1 v test.c:13 4
+BITCODE-NEXT: fn1 x test.c:3 13
+BITCODE-NEXT: fn1 y test.c:3 13
+
+RUN: llvm-dwarfdump --show-variable-coverage --variable-coverage-bitcode-file=%S/Inputs/coverage.ll --coverage-baseline %t.o %t-opt.o | FileCheck %s --check-prefix=BASELINE-BITCODE
+
+BASELINE-BITCODE:      Variable coverage statistics:
+BASELINE-BITCODE-NEXT: Function InlChain Variable Decl LinesCovered Baseline CoveredRatio LinesPresent LinesPresentRatio
+BASELINE-BITCODE-NEXT: f k test.c:20 2 2 1 2 1
+BASELINE-BITCODE-NEXT: f l test.c:20 3 3 1 3 1
+BASELINE-BITCODE-NEXT: fn1 a test.c:11 3 6 0.5 3 0.5
+BASELINE-BITCODE-NEXT: fn1 u test.c:4 1 12 0.0833 5 0.417
+BASELINE-BITCODE-NEXT: fn1 v test.c:13 0 4 0 2 0.5
+BASELINE-BITCODE-NEXT: fn1 x test.c:3 6 13 0.462 6 0.462
+BASELINE-BITCODE-NEXT: fn1 y test.c:3 6 13 0.462 6 0.462
diff --git a/llvm/tools/llvm-dwarfdump/Coverage.cpp b/llvm/tools/llvm-dwarfdump/Coverage.cpp
index 35ea9bc0a1deb..5d4cad37da96b 100644
--- a/llvm/tools/llvm-dwarfdump/Coverage.cpp
+++ b/llvm/tools/llvm-dwarfdump/Coverage.cpp
@@ -27,8 +27,12 @@ using namespace llvm;
 using namespace llvm::dwarf;
 using namespace llvm::object;
 
+typedef std::pair<std::string, std::string> StringPair;
 /// Pair of file index and line number representing a source location.
 typedef std::pair<uint16_t, size_t> SourceLocation;
+typedef std::map<StringPair,
+                 std::optional<DenseSet<std::pair<StringRef, uint32_t>>>>
+    LineMap;
 
 /// Adds source locations to the line set that correspond to an address range.
 static void addLines(const DWARFDebugLine::LineTable *LineTable,
@@ -48,9 +52,9 @@ static void addLines(const DWARFDebugLine::LineTable *LineTable,
   }
 }
 
-// Converts the file index of each line in the set to use our own internal
-// file index. This is required for a reliable comparison as the DWARF index may
-// differ across compilations.
+/// Converts the file index of each line in the set to use our own internal
+/// file index. This is required for a reliable comparison as the DWARF index
+/// may differ across compilations.
 static DenseSet<SourceLocation>
 convertFileIndices(DenseSet<SourceLocation> Lines,
                    const DWARFDebugLine::LineTable *const LineTable,
@@ -89,11 +93,10 @@ convertFileIndices(DenseSet<SourceLocation> Lines,
 /// Returns the set of source lines covered by a variable's debug information,
 /// computed by intersecting the variable's location ranges and the containing
 /// scope's address ranges.
-static DenseSet<SourceLocation>
-computeVariableCoverage(DWARFDie VariableDIE,
-                        const DWARFDebugLine::LineTable *const LineTable,
-                        DenseMap<uint16_t, uint16_t> &FileIndexMap,
-                        StringMap<uint16_t> &FileNameMap) {
+static DenseSet<SourceLocation> computeVariableCoverage(
+    DWARFDie VariableDIE, const DWARFDebugLine::LineTable *const LineTable,
+    DenseMap<uint16_t, uint16_t> &FileIndexMap,
+    StringMap<uint16_t> &FileNameMap, LineMap::value_type *DefinedLines) {
   // The optionals below will be empty if no address ranges were found, and
   // present (but containing an empty set) if ranges were found but contained no
   // source locations, in order to distinguish the two cases.
@@ -132,11 +135,28 @@ computeVariableCoverage(DWARFDie VariableDIE,
   else if (ParentLines)
     set_intersect(*Lines, *ParentLines);
 
-  if (!Lines)
-    return {};
+  auto ResultLines =
+      convertFileIndices(Lines.value_or(DenseSet<SourceLocation>()), LineTable,
+                         FileIndexMap, FileNameMap);
+
+  if (DefinedLines) {
+    // Remove any lines where the variable does not have a defined value.
+    auto &DL = DefinedLines->second;
+    if (DL) {
+      DenseSet<SourceLocation> IndexLines;
+      for (const auto &L : *DL) {
+        auto NameIt = FileNameMap.find(L.first);
+        if (NameIt != FileNameMap.end())
+          IndexLines.insert({NameIt->second, L.second});
+      }
+      if (!Lines)
+        ResultLines = std::move(IndexLines);
+      else
+        set_intersect(ResultLines, IndexLines);
+    }
+  }
 
-  return convertFileIndices(Lines.value_or(DenseSet<SourceLocation>()),
-                            LineTable, FileIndexMap, FileNameMap);
+  return ResultLines;
 }
 
 /// Adds source locations to the line set that are within an inlined subroutine.
@@ -195,6 +215,124 @@ static const SmallVector<DWARFDie> getParentSubroutines(DWARFDie DIE) {
   return Parents;
 }
 
+static bool isInScope(MDNode *Scope, const DebugLoc &Loc) {
+  MDNode *Parent = Loc.getScope();
+  while (Parent != Scope) {
+    auto *S = dyn_cast_if_present<DIScope>(Parent);
+    if (!S)
+      return false;
+    Parent = S->getScope();
+  }
+  return true;
+}
+
+static bool isLoop(BasicBlock *Origin, BasicBlock *BB,
+                   SmallPtrSet<BasicBlock *, 8> &Visited) {
+  for (auto *P : predecessors(BB)) {
+    if (P == Origin)
+      return true;
+    if (!Visited.count(P)) {
+      Visited.insert(P);
+      if (isLoop(Origin, P, Visited))
+        return true;
+    }
+  }
+  return false;
+}
+
+struct VarState {
+  DbgVariableRecord &DVR;
+  DenseSet<std::pair<StringRef, uint32_t>> Lines;
+  SmallPtrSet<BasicBlock *, 8> LiveOut;
+};
+
+/// Given an instruction that stores to a variable and its basic block,
+/// recursively searches its successor instructions/basic blocks and adds lines
+/// where the variable has a defined value to the variable's line set.
+static void getSuccessorLines(VarState &Var, BasicBlock *BB, Instruction *I) {
+  // Process the basic block if it contains the store instruction or the
+  // variable is defined in all of its predecessors, excluding any that are part
+  // of a loop that contains the current block.
+  bool ShouldProcess = I;
+  if (!ShouldProcess) {
+    ShouldProcess = true;
+    for (auto *P : predecessors(BB)) {
+      SmallPtrSet<BasicBlock *, 8> Visited;
+      if (!Var.LiveOut.count(P) && !isLoop(BB, P, Visited)) {
+        ShouldProcess = false;
+        break;
+      }
+    }
+  }
+  if (!ShouldProcess || Var.LiveOut.count(BB))
+    return;
+  Var.LiveOut.insert(BB);
+
+  // Add lines that are within the variable's scope, starting from the store
+  // instruction or the start of the block if this is a successor block.
+  auto *Next = I ? I : &BB->front();
+  auto *VarScope = Var.DVR.getVariable()->getScope();
+  do {
+    auto &Loc = Next->getDebugLoc();
+    DIScope *Scope;
+    if (Loc && isInScope(VarScope, Loc) && Loc.getLine() &&
+        (Scope = dyn_cast_if_present<DIScope>(Loc.getScope()))) {
+      Var.Lines.insert({Scope->getFilename(), Loc.getLine()});
+    }
+  } while ((Next = Next->getNextNode()));
+  for (auto *S : successors(BB))
+    getSuccessorLines(Var, S, nullptr);
+}
+
+/// Computes the defined lines of all variables in an IR module.
+static LineMap processModule(Module *Mod) {
+  LineMap Result;
+
+  for (auto &F : Mod->functions()) {
+    std::vector<VarState> Vars;
+    for (auto &BB : F) {
+      for (auto &I : BB) {
+        for (DbgVariableRecord &DVR : filterDbgVars(I.getDbgRecordRange())) {
+          if (DVR.isDbgDeclare()) {
+            // For #dbg_declare, don't treat the variable as live until we find
+            // a store to it.
+            Vars.push_back({DVR, {}, {}});
+          } else if (DVR.isDbgValue()) {
+            // For #dbg_value, the variable is live immediately from this point.
+            auto Var = find_if(Vars, [&](auto &Var) {
+              return Var.DVR.getVariable() == DVR.getVariable();
+            });
+            if (Var != Vars.end()) {
+              getSuccessorLines(*Var, &BB, &I);
+            } else {
+              Vars.push_back({DVR, {}, {}});
+              getSuccessorLines(Vars.back(), &BB, &I);
+            }
+          }
+        }
+      }
+    }
+
+    // Search for stores to any declared variables. For the purposes of this
+    // analysis, we consider any instruction that isn't a load and has the
+    // variable as an operand to potentially store to it.
+    for (auto &BB : F)
+      for (auto &I : BB)
+        if (I.getOpcode() != Instruction::Load)
+          for (auto *Value : I.operand_values())
+            for (auto &Var : Vars)
+              if (Value == Var.DVR.getValue())
+                // The variable is live from the instruction after the store.
+                getSuccessorLines(Var, &BB, I.getNextNode());
+
+    for (auto &Var : Vars) {
+      StringPair Key(F.getName(), Var.DVR.getVariable()->getName());
+      Result.emplace(Key, Var.Lines);
+    }
+  }
+  return Result;
+}
+
 struct VarKey {
   const char *const SubprogramName;
   const char *const Name;
@@ -295,7 +433,19 @@ static void displayVariableCoverage(const VarKey &Key, const VarCoverage &Var,
 bool dwarfdump::showVariableCoverage(ObjectFile &Obj, DWARFContext &DICtx,
                                      ObjectFile *BaselineObj,
                                      DWARFContext *BaselineCtx,
+                                     StringRef BitcodeFile,
                                      bool CombineInstances, raw_ostream &OS) {
+  LineMap LM;
+  LLVMContext Context;
+  if (!BitcodeFile.empty()) {
+    SMDiagnostic Err;
+    std::unique_ptr<Module> Mod = parseIRFile(BitcodeFile, Err, Context);
+    if (!Err.getMessage().empty())
+      Err.print("llvm-dwarfdump", OS);
+    else
+      LM = processModule(Mod.get());
+  }
+
   BaselineVarMap BaselineVars;
   StringMap<uint16_t> FileNameMap;
 
@@ -317,8 +467,10 @@ bool dwarfdump::showVariableCoverage(ObjectFile &Obj, DWARFContext &DICtx,
         if (!Key)
           continue;
 
-        auto Cov =
-            computeVariableCoverage(VariableDIE, LT, FileIndexMap, FileNameMap);
+        const auto DefinedLines = LM.find({Key->SubprogramName, Key->Name});
+        auto Cov = computeVariableCoverage(
+            VariableDIE, LT, FileIndexMap, FileNameMap,
+            DefinedLines != LM.end() ? &*DefinedLines : nullptr);
         const auto SubroutineCov = computeSubroutineCoverage(
             SubroutineDIE, LT, FileIndexMap, FileNameMap);
         set_intersect(Cov, SubroutineCov);
@@ -349,8 +501,10 @@ bool dwarfdump::showVariableCoverage(ObjectFile &Obj, DWARFContext &DICtx,
       if (!Key)
         continue;
 
-      auto Cov =
-          computeVariableCoverage(VariableDIE, LT, FileIndexMap, FileNameMap);
+      const auto DefinedLines = LM.find({Key->SubprogramName, Key->Name});
+      auto Cov = computeVariableCoverage(
+          VariableDIE, LT, FileIndexMap, FileNameMap,
+          DefinedLines != LM.end() ? &*DefinedLines : nullptr);
       const auto SubroutineCov = computeSubroutineCoverage(
           SubroutineDIE, LT, FileIndexMap, FileNameMap);
       set_intersect(Cov, SubroutineCov);
diff --git a/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.cpp b/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.cpp
index 88e5fb33471a6..dae922820560f 100644
--- a/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.cpp
+++ b/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.cpp
@@ -343,6 +343,11 @@ static opt<std::string>
                      desc("File to use as the baseline for variable coverage "
                           "statistics (implies --show-variable-coverage)"),
                      value_desc("filename"), cat(DwarfDumpCategory));
+static opt<std::string> BitcodeFile(
+    "variable-coverage-bitcode-file",
+    desc("File containing bitcode used for calculating variable definedness in "
+         "coverage statistics (implies --show-variable-coverage)"),
+    value_desc("filename"), cat(DwarfDumpCategory));
 static opt<bool> CombineInstances(
     "combine-inline-variable-instances",
     desc(
@@ -980,17 +985,17 @@ int main(int argc, char **argv) {
       auto showCoverage = [&](ObjectFile &Obj, DWARFContext &DICtx,
                               const Twine &Filename, raw_ostream &OS) {
         return showVariableCoverage(Obj, DICtx, &BaselineObj, &BaselineCtx,
-                                    CombineInstances, OS);
+                                    BitcodeFile, CombineInstances, OS);
       };
       for (StringRef Object : Objects)
         Success &= handleFile(Object, showCoverage, OutputFile.os());
       return true;
     };
     Success &= handleFile(CoverageBaseline, handleBaseline, OutputFile.os());
-  } else if (ShowVariableCoverage) {
+  } else if (ShowVariableCoverage || !BitcodeFile.empty()) {
     auto showCoverage = [&](ObjectFile &Obj, DWARFContext &DICtx,
                             const Twine &Filename, raw_ostream &OS) {
-      return showVariableCoverage(Obj, DICtx, nullptr, nullptr,
+      return showVariableCoverage(Obj, DICtx, nullptr, nullptr, BitcodeFile,
                                   CombineInstances, OS);
     };
     for (StringRef Object : Objects)
diff --git a/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.h b/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.h
index 90a49e8f75ffc..e9d0433cc3f49 100644
--- a/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.h
+++ b/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.h
@@ -41,8 +41,8 @@ bool collectObjectSectionSizes(object::ObjectFile &Obj, DWARFContext &DICtx,
 
 bool showVariableCoverage(object::ObjectFile &Obj, DWARFContext &DICtx,
                           object::ObjectFile *BaselineObj,
-                          DWARFContext *BaselineCtx, bool CombineInstances,
-                          raw_ostream &OS);
+                          DWARFContext *BaselineCtx, StringRef BitcodeFile,
+                          bool CombineInstances, raw_ostream &OS);
 } // namespace dwarfdump
 } // namespace llvm
 

>From 09ba578b26246812b33bf5ed8eba835df8fd4649 Mon Sep 17 00:00:00 2001
From: unexpectedlydefined <unexpectedlydefined at gmail.com>
Date: Tue, 26 May 2026 19:26:01 +0100
Subject: [PATCH 2/2] fixup! Add IR analysis for variable coverage

---
 llvm/docs/CommandGuide/llvm-dwarfdump.rst    |   5 +-
 llvm/tools/llvm-dwarfdump/Coverage.cpp       | 173 +++++++++++--------
 llvm/tools/llvm-dwarfdump/llvm-dwarfdump.cpp |  13 +-
 3 files changed, 109 insertions(+), 82 deletions(-)

diff --git a/llvm/docs/CommandGuide/llvm-dwarfdump.rst b/llvm/docs/CommandGuide/llvm-dwarfdump.rst
index ecf59d335be8c..22f2edfc30f57 100644
--- a/llvm/docs/CommandGuide/llvm-dwarfdump.rst
+++ b/llvm/docs/CommandGuide/llvm-dwarfdump.rst
@@ -205,8 +205,9 @@ OPTIONS
 
 .. option:: --variable-coverage-bitcode-file
 
-            File containing bitcode used for calculating variable definedness
-            in coverage statistics (implies :option:`--show-variable-coverage`)
+            File containing LLVM IR (bitcode or textual) used for calculating
+            variable definedness in coverage statistics (implies
+            :option:`--show-variable-coverage`)
 
 .. option:: --combine-inline-variable-instances
 
diff --git a/llvm/tools/llvm-dwarfdump/Coverage.cpp b/llvm/tools/llvm-dwarfdump/Coverage.cpp
index 5d4cad37da96b..b371132595ac3 100644
--- a/llvm/tools/llvm-dwarfdump/Coverage.cpp
+++ b/llvm/tools/llvm-dwarfdump/Coverage.cpp
@@ -14,6 +14,7 @@
 #include "llvm/DebugInfo/DWARF/DWARFCompileUnit.h"
 #include "llvm/DebugInfo/DWARF/DWARFContext.h"
 #include "llvm/IR/CFG.h"
+#include "llvm/IR/DebugInfo.h"
 #include "llvm/IR/DebugInfoMetadata.h"
 #include "llvm/IR/DebugProgramInstruction.h"
 #include "llvm/IR/Instructions.h"
@@ -27,12 +28,15 @@ using namespace llvm;
 using namespace llvm::dwarf;
 using namespace llvm::object;
 
-typedef std::pair<std::string, std::string> StringPair;
 /// Pair of file index and line number representing a source location.
 typedef std::pair<uint16_t, size_t> SourceLocation;
-typedef std::map<StringPair,
-                 std::optional<DenseSet<std::pair<StringRef, uint32_t>>>>
-    LineMap;
+/// Pair of subroutine name and variable name representing a local variable.
+typedef std::pair<std::string, std::string> BitcodeVarKey;
+/// Pair of file name and line number representing a source location.
+typedef std::pair<StringRef, uint32_t> BitcodeSourceLocation;
+/// Maps local variables found in the bitcode to a set of source locations.
+typedef std::map<BitcodeVarKey, std::optional<DenseSet<BitcodeSourceLocation>>>
+    BitcodeLineMap;
 
 /// Adds source locations to the line set that correspond to an address range.
 static void addLines(const DWARFDebugLine::LineTable *LineTable,
@@ -93,10 +97,12 @@ convertFileIndices(DenseSet<SourceLocation> Lines,
 /// Returns the set of source lines covered by a variable's debug information,
 /// computed by intersecting the variable's location ranges and the containing
 /// scope's address ranges.
-static DenseSet<SourceLocation> computeVariableCoverage(
-    DWARFDie VariableDIE, const DWARFDebugLine::LineTable *const LineTable,
-    DenseMap<uint16_t, uint16_t> &FileIndexMap,
-    StringMap<uint16_t> &FileNameMap, LineMap::value_type *DefinedLines) {
+static DenseSet<SourceLocation>
+computeVariableCoverage(DWARFDie VariableDIE,
+                        const DWARFDebugLine::LineTable *const LineTable,
+                        DenseMap<uint16_t, uint16_t> &FileIndexMap,
+                        StringMap<uint16_t> &FileNameMap,
+                        BitcodeLineMap::value_type *DefinedLines) {
   // The optionals below will be empty if no address ranges were found, and
   // present (but containing an empty set) if ranges were found but contained no
   // source locations, in order to distinguish the two cases.
@@ -150,7 +156,7 @@ static DenseSet<SourceLocation> computeVariableCoverage(
           IndexLines.insert({NameIt->second, L.second});
       }
       if (!Lines)
-        ResultLines = std::move(IndexLines);
+        assert("Source lines found in bitcode but not in DWARF");
       else
         set_intersect(ResultLines, IndexLines);
     }
@@ -226,108 +232,127 @@ static bool isInScope(MDNode *Scope, const DebugLoc &Loc) {
   return true;
 }
 
-static bool isLoop(BasicBlock *Origin, BasicBlock *BB,
-                   SmallPtrSet<BasicBlock *, 8> &Visited) {
-  for (auto *P : predecessors(BB)) {
-    if (P == Origin)
+/// Determines whether an instruction stores to a location. For the purposes of
+/// this analysis, we consider any call-like instruction with the location as an
+/// argument to be a store to it.
+static bool isStoreToLocation(const DataLayout &DL, Instruction &I,
+                              Value *Loc) {
+  std::optional<at::AssignmentInfo> Info;
+  if (StoreInst *SI = dyn_cast<StoreInst>(&I)) {
+    if (SI->getPointerOperand() == Loc)
       return true;
-    if (!Visited.count(P)) {
-      Visited.insert(P);
-      if (isLoop(Origin, P, Visited))
-        return true;
-    }
+    Info = at::getAssignmentInfo(DL, SI);
+  } else if (MemIntrinsic *MI = dyn_cast<MemIntrinsic>(&I)) {
+    if (MI->getDest() == Loc)
+      return true;
+    Info = at::getAssignmentInfo(DL, MI);
+  } else if (CallBase *CI = dyn_cast<CallBase>(&I)) {
+    return CI->hasArgument(Loc);
   }
-  return false;
+  return Info && Info->Base == Loc;
 }
 
+typedef SmallDenseMap<BasicBlock *, Instruction *, 8> VarDefinitionMap;
+
 struct VarState {
   DbgVariableRecord &DVR;
-  DenseSet<std::pair<StringRef, uint32_t>> Lines;
-  SmallPtrSet<BasicBlock *, 8> LiveOut;
+  VarDefinitionMap Definitions;
 };
 
-/// Given an instruction that stores to a variable and its basic block,
-/// recursively searches its successor instructions/basic blocks and adds lines
-/// where the variable has a defined value to the variable's line set.
-static void getSuccessorLines(VarState &Var, BasicBlock *BB, Instruction *I) {
-  // Process the basic block if it contains the store instruction or the
-  // variable is defined in all of its predecessors, excluding any that are part
-  // of a loop that contains the current block.
-  bool ShouldProcess = I;
-  if (!ShouldProcess) {
-    ShouldProcess = true;
-    for (auto *P : predecessors(BB)) {
-      SmallPtrSet<BasicBlock *, 8> Visited;
-      if (!Var.LiveOut.count(P) && !isLoop(BB, P, Visited)) {
-        ShouldProcess = false;
-        break;
-      }
-    }
-  }
-  if (!ShouldProcess || Var.LiveOut.count(BB))
-    return;
-  Var.LiveOut.insert(BB);
-
-  // Add lines that are within the variable's scope, starting from the store
-  // instruction or the start of the block if this is a successor block.
-  auto *Next = I ? I : &BB->front();
+/// Adds source locations to the line set for instructions in a basic block,
+/// starting with a specific instruction.
+static void addModuleLines(Instruction *I, VarState &Var,
+                           DenseSet<std::pair<StringRef, uint32_t>> &Lines) {
   auto *VarScope = Var.DVR.getVariable()->getScope();
   do {
-    auto &Loc = Next->getDebugLoc();
+    auto &Loc = I->getDebugLoc();
     DIScope *Scope;
     if (Loc && isInScope(VarScope, Loc) && Loc.getLine() &&
         (Scope = dyn_cast_if_present<DIScope>(Loc.getScope()))) {
-      Var.Lines.insert({Scope->getFilename(), Loc.getLine()});
+      Lines.insert({Scope->getFilename(), Loc.getLine()});
     }
-  } while ((Next = Next->getNextNode()));
-  for (auto *S : successors(BB))
-    getSuccessorLines(Var, S, nullptr);
+  } while ((I = I->getNextNode()));
 }
 
 /// Computes the defined lines of all variables in an IR module.
-static LineMap processModule(Module *Mod) {
-  LineMap Result;
-
+static BitcodeLineMap processModule(Module *Mod) {
+  BitcodeLineMap Result;
+  std::vector<VarState> Vars;
   for (auto &F : Mod->functions()) {
-    std::vector<VarState> Vars;
+    Vars.clear();
     for (auto &BB : F) {
       for (auto &I : BB) {
         for (DbgVariableRecord &DVR : filterDbgVars(I.getDbgRecordRange())) {
+          if (DVR.isKillLocation()) {
+            assert("Variable in bitcode has been optimized out");
+            continue;
+          }
           if (DVR.isDbgDeclare()) {
             // For #dbg_declare, don't treat the variable as live until we find
             // a store to it.
-            Vars.push_back({DVR, {}, {}});
+            Vars.push_back(VarState{DVR, VarDefinitionMap()});
           } else if (DVR.isDbgValue()) {
             // For #dbg_value, the variable is live immediately from this point.
+            if (DVR.getDebugLoc().getInlinedAt() != nullptr) {
+              assert("Variable in bitcode has been inlined");
+              continue;
+            }
             auto Var = find_if(Vars, [&](auto &Var) {
               return Var.DVR.getVariable() == DVR.getVariable();
             });
-            if (Var != Vars.end()) {
-              getSuccessorLines(*Var, &BB, &I);
-            } else {
-              Vars.push_back({DVR, {}, {}});
-              getSuccessorLines(Vars.back(), &BB, &I);
-            }
+            if (Var != Vars.end())
+              // If a basic block contains multiple stores to a variable, use
+              // the earliest one by allowing the insertion to silently fail if
+              // the basic block is already in the map.
+              Var->Definitions.insert({&BB, &I});
+            else
+              Vars.push_back(VarState{DVR, {{&BB, &I}}});
           }
         }
       }
     }
 
-    // Search for stores to any declared variables. For the purposes of this
-    // analysis, we consider any instruction that isn't a load and has the
-    // variable as an operand to potentially store to it.
     for (auto &BB : F)
       for (auto &I : BB)
-        if (I.getOpcode() != Instruction::Load)
-          for (auto *Value : I.operand_values())
-            for (auto &Var : Vars)
-              if (Value == Var.DVR.getValue())
-                // The variable is live from the instruction after the store.
-                getSuccessorLines(Var, &BB, I.getNextNode());
+        for (auto &Var : Vars)
+          if (isStoreToLocation(Mod->getDataLayout(), I, Var.DVR.getValue()))
+            // The variable is live from the instruction after the store. As
+            // above, the earliest store in this basic block will be used.
+            Var.Definitions.insert({&BB, I.getNextNode()});
 
     for (auto &Var : Vars) {
-      StringPair Key(F.getName(), Var.DVR.getVariable()->getName());
-      Result.emplace(Key, Var.Lines);
+      SmallPtrSet<BasicBlock *, 8> Visited;
+      DenseSet<std::pair<StringRef, uint32_t>> Lines;
+
+      // Visit all basic blocks that are reachable from the entry block without
+      // going through a block that stores to the variable.
+      SmallVector<BasicBlock *> BlocksToVisit{&F.getEntryBlock()};
+      while (!BlocksToVisit.empty()) {
+        BasicBlock *BB = BlocksToVisit.pop_back_val();
+        if (!Visited.insert(BB).second)
+          continue;
+
+        auto I = Var.Definitions.find(BB);
+        if (I != Var.Definitions.end()) {
+          // Block contains a definition: add all lines after it to the set
+          if (I->second != nullptr)
+            addModuleLines(I->second, Var, Lines);
+        } else {
+          // Block does not contain a definition: visit its successors
+          auto S = successors(BB);
+          BlocksToVisit.append(S.begin(), S.end());
+        }
+      }
+
+      // All unvisited basic blocks must only be reachable by going through a
+      // block that stores to the variable, so add lines to the set for all of
+      // their instructions.
+      for (auto &BB : F)
+        if (!Visited.count(&BB))
+          addModuleLines(&*BB.begin(), Var, Lines);
+
+      BitcodeVarKey Key(F.getName(), Var.DVR.getVariable()->getName());
+      Result.emplace(Key, Lines);
     }
   }
   return Result;
@@ -435,7 +460,7 @@ bool dwarfdump::showVariableCoverage(ObjectFile &Obj, DWARFContext &DICtx,
                                      DWARFContext *BaselineCtx,
                                      StringRef BitcodeFile,
                                      bool CombineInstances, raw_ostream &OS) {
-  LineMap LM;
+  BitcodeLineMap LM;
   LLVMContext Context;
   if (!BitcodeFile.empty()) {
     SMDiagnostic Err;
diff --git a/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.cpp b/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.cpp
index dae922820560f..8c7387e18e60c 100644
--- a/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.cpp
+++ b/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.cpp
@@ -343,11 +343,12 @@ static opt<std::string>
                      desc("File to use as the baseline for variable coverage "
                           "statistics (implies --show-variable-coverage)"),
                      value_desc("filename"), cat(DwarfDumpCategory));
-static opt<std::string> BitcodeFile(
-    "variable-coverage-bitcode-file",
-    desc("File containing bitcode used for calculating variable definedness in "
-         "coverage statistics (implies --show-variable-coverage)"),
-    value_desc("filename"), cat(DwarfDumpCategory));
+static opt<std::string>
+    BitcodeFile("variable-coverage-bitcode-file",
+                desc("File containing LLVM IR (bitcode or textual) used for "
+                     "calculating variable definedness in coverage statistics "
+                     "(implies --show-variable-coverage)"),
+                value_desc("filename"), cat(DwarfDumpCategory));
 static opt<bool> CombineInstances(
     "combine-inline-variable-instances",
     desc(
@@ -926,7 +927,7 @@ int main(int argc, char **argv) {
   if (DumpAll)
     DumpType = DIDT_All;
   if (DumpType == DIDT_Null && !ShowVariableCoverage &&
-      CoverageBaseline.empty()) {
+      CoverageBaseline.empty() && BitcodeFile.empty()) {
     if (Verbose || Verify)
       DumpType = DIDT_All;
     else



More information about the llvm-commits mailing list