[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