[llvm] [llvm-dwarfdump][LineCov 1/3] Add variable coverage metrics (PR #169646)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Nov 26 05:05:40 PST 2025
https://github.com/unexpectedlydefined updated https://github.com/llvm/llvm-project/pull/169646
>From eeb5e6213e4841bfaae67397cb7c16f32e31079e Mon Sep 17 00:00:00 2001
From: unexpectedlydefined <unexpectedlydefined at gmail.com>
Date: Tue, 4 Nov 2025 13:20:36 +0000
Subject: [PATCH 1/3] Add line table coverage metrics
---
llvm/docs/CommandGuide/llvm-dwarfdump.rst | 37 +++
.../llvm-dwarfdump/X86/Inputs/coverage-opt.ll | 134 ++++++++++
.../llvm-dwarfdump/X86/Inputs/coverage.ll | 170 ++++++++++++
.../tools/llvm-dwarfdump/X86/coverage.test | 24 ++
llvm/tools/llvm-dwarfdump/CMakeLists.txt | 1 +
llvm/tools/llvm-dwarfdump/Coverage.cpp | 244 ++++++++++++++++++
llvm/tools/llvm-dwarfdump/llvm-dwarfdump.cpp | 21 +-
llvm/tools/llvm-dwarfdump/llvm-dwarfdump.h | 2 +
8 files changed, 632 insertions(+), 1 deletion(-)
create mode 100644 llvm/test/tools/llvm-dwarfdump/X86/Inputs/coverage-opt.ll
create mode 100644 llvm/test/tools/llvm-dwarfdump/X86/Inputs/coverage.ll
create mode 100644 llvm/test/tools/llvm-dwarfdump/X86/coverage.test
create mode 100644 llvm/tools/llvm-dwarfdump/Coverage.cpp
diff --git a/llvm/docs/CommandGuide/llvm-dwarfdump.rst b/llvm/docs/CommandGuide/llvm-dwarfdump.rst
index dfc0431f07826..c3af617d8b6c2 100644
--- a/llvm/docs/CommandGuide/llvm-dwarfdump.rst
+++ b/llvm/docs/CommandGuide/llvm-dwarfdump.rst
@@ -193,6 +193,17 @@ OPTIONS
The :option:`--debug-frame` and :option:`--eh-frame` options are aliases, in cases where both sections are present one command outputs both.
+.. option:: --show-variable-coverage
+
+ Show per-variable coverage metrics. The output format is described
+ in the section below (:ref:`variable-coverage-format`).
+
+.. option:: --combine-instances
+
+ Use with :option:`--show-variable-coverage` to average variable
+ coverage across inlined subroutine instances instead of printing
+ them separately.
+
.. option:: @<FILE>
Read command-line options from `<FILE>`.
@@ -243,6 +254,32 @@ The following is generated if there are no errors reported::
"error-count": 0
}
+.. _variable-coverage-format:
+
+FORMAT OF VARIABLE COVERAGE OUTPUT
+---------------------------
+
+The :option:`--show-variable-coverage` option differs from
+:option:`--statistics` by printing per-variable debug info coverage metrics
+based on the number of source lines covered instead of the number of
+instruction bytes. Compared to counting instruction bytes, this is more stable
+across compilations and better reflects the debugging experience. The output is
+a tab-separated table containing the following columns:
+
+ - `Function` ==> Name of the function the variable was found in
+ - `InstanceCount` (when :option:`--combine-instances` is specified) ==>
+ Number of instances of the function; this is 1 for functions that have
+ not been inlined, and n+1 for functions that have been inlined n times
+ - `InlChain` (when :option:`--combine-instances` is not specified) ==>
+ Chain of call sites (file and line number) that the function has been
+ inlined into; this will be empty if the function has not been inlined
+ - `Variable` ==> Name of the variable
+ - `Decl` ==> Source location (file and line number) of the variable's
+ declaration
+ - `LinesCovered` ==> Number of source lines covered by the variable's
+ debug information in the input file
+
+
EXIT STATUS
-----------
diff --git a/llvm/test/tools/llvm-dwarfdump/X86/Inputs/coverage-opt.ll b/llvm/test/tools/llvm-dwarfdump/X86/Inputs/coverage-opt.ll
new file mode 100644
index 0000000000000..81e7feaf1addc
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfdump/X86/Inputs/coverage-opt.ll
@@ -0,0 +1,134 @@
+; The source code of the test case:
+; extern void fn3(int *);
+; extern void fn2 (int);
+; __attribute__((noinline))
+; void
+; fn1 (int x, int y)
+; {
+; int u = x + y;
+; if (x > 1)
+; u += 1;
+; else
+; u += 2;
+; if (y > 4)
+; u += x;
+; int a = 7;
+; fn2 (a);
+; u --;
+; }
+
+; __attribute__((noinline))
+; int f()
+; {
+; int l, k;
+; fn3(&l);
+; fn3(&k);
+; fn1 (l, k);
+; return 0;
+; }
+
+; ModuleID = 'test.c'
+source_filename = "test.c"
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+; Function Attrs: noinline nounwind uwtable
+define dso_local void @fn1(i32 noundef %0, i32 noundef %1) local_unnamed_addr !dbg !9 {
+ call void @llvm.dbg.value(metadata i32 %0, metadata !14, metadata !DIExpression()), !dbg !18
+ call void @llvm.dbg.value(metadata i32 %1, metadata !15, metadata !DIExpression()), !dbg !18
+ call void @llvm.dbg.value(metadata !DIArgList(i32 %0, i32 %1), metadata !16, metadata !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 1, DW_OP_plus, DW_OP_stack_value)), !dbg !18
+ call void @llvm.dbg.value(metadata i32 undef, metadata !16, metadata !DIExpression()), !dbg !18
+ call void @llvm.dbg.value(metadata i32 undef, metadata !16, metadata !DIExpression()), !dbg !18
+ call void @llvm.dbg.value(metadata i32 7, metadata !17, metadata !DIExpression()), !dbg !18
+ tail call void @fn2(i32 noundef 7), !dbg !19
+ call void @llvm.dbg.value(metadata i32 undef, metadata !16, metadata !DIExpression(DW_OP_constu, 1, DW_OP_minus, DW_OP_stack_value)), !dbg !18
+ ret void, !dbg !20
+}
+
+; Function Attrs: argmemonly mustprogress nofree nosync nounwind willreturn
+declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture)
+
+declare !dbg !21 void @fn2(i32 noundef) local_unnamed_addr
+
+; Function Attrs: argmemonly mustprogress nofree nosync nounwind willreturn
+declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture)
+
+; Function Attrs: noinline nounwind uwtable
+define dso_local i32 @f() local_unnamed_addr !dbg !25 {
+ %1 = alloca i32, align 4
+ %2 = alloca i32, align 4
+ %3 = bitcast i32* %1 to i8*, !dbg !31
+ call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull %3), !dbg !31
+ %4 = bitcast i32* %2 to i8*, !dbg !31
+ call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull %4), !dbg !31
+ call void @llvm.dbg.value(metadata i32* %1, metadata !29, metadata !DIExpression(DW_OP_deref)), !dbg !32
+ call void @fn3(i32* noundef nonnull %1), !dbg !33
+ call void @llvm.dbg.value(metadata i32* %2, metadata !30, metadata !DIExpression(DW_OP_deref)), !dbg !32
+ call void @fn3(i32* noundef nonnull %2), !dbg !34
+ %5 = load i32, i32* %1, align 4, !dbg !35, !tbaa !36
+ call void @llvm.dbg.value(metadata i32 %5, metadata !29, metadata !DIExpression()), !dbg !32
+ %6 = load i32, i32* %2, align 4, !dbg !40, !tbaa !36
+ call void @llvm.dbg.value(metadata i32 %6, metadata !30, metadata !DIExpression()), !dbg !32
+ call void @fn1(i32 noundef %5, i32 noundef %6), !dbg !41
+ call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull %4), !dbg !42
+ call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull %3), !dbg !42
+ ret i32 0, !dbg !43
+}
+
+declare !dbg !44 void @fn3(i32* noundef) local_unnamed_addr
+
+; Function Attrs: nofree nosync nounwind readnone speculatable willreturn
+declare void @llvm.dbg.value(metadata, metadata, metadata)
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3, !4, !5, !6, !7}
+!llvm.ident = !{!8}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 14.0.6", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "test.c", directory: "/")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !{i32 1, !"wchar_size", i32 4}
+!5 = !{i32 7, !"PIC Level", i32 2}
+!6 = !{i32 7, !"PIE Level", i32 2}
+!7 = !{i32 7, !"uwtable", i32 1}
+!8 = !{!"clang version 14.0.6"}
+!9 = distinct !DISubprogram(name: "fn1", scope: !1, file: !1, line: 5, type: !10, scopeLine: 6, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !13)
+!10 = !DISubroutineType(types: !11)
+!11 = !{null, !12, !12}
+!12 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!13 = !{!14, !15, !16, !17}
+!14 = !DILocalVariable(name: "x", arg: 1, scope: !9, file: !1, line: 5, type: !12)
+!15 = !DILocalVariable(name: "y", arg: 2, scope: !9, file: !1, line: 5, type: !12)
+!16 = !DILocalVariable(name: "u", scope: !9, file: !1, line: 7, type: !12)
+!17 = !DILocalVariable(name: "a", scope: !9, file: !1, line: 14, type: !12)
+!18 = !DILocation(line: 0, scope: !9)
+!19 = !DILocation(line: 15, column: 3, scope: !9)
+!20 = !DILocation(line: 17, column: 1, scope: !9)
+!21 = !DISubprogram(name: "fn2", scope: !1, file: !1, line: 2, type: !22, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !24)
+!22 = !DISubroutineType(types: !23)
+!23 = !{null, !12}
+!24 = !{}
+!25 = distinct !DISubprogram(name: "f", scope: !1, file: !1, line: 20, type: !26, scopeLine: 21, flags: DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !28)
+!26 = !DISubroutineType(types: !27)
+!27 = !{!12}
+!28 = !{!29, !30}
+!29 = !DILocalVariable(name: "l", scope: !25, file: !1, line: 22, type: !12)
+!30 = !DILocalVariable(name: "k", scope: !25, file: !1, line: 22, type: !12)
+!31 = !DILocation(line: 22, column: 3, scope: !25)
+!32 = !DILocation(line: 0, scope: !25)
+!33 = !DILocation(line: 23, column: 3, scope: !25)
+!34 = !DILocation(line: 24, column: 3, scope: !25)
+!35 = !DILocation(line: 25, column: 8, scope: !25)
+!36 = !{!37, !37, i64 0}
+!37 = !{!"int", !38, i64 0}
+!38 = !{!"omnipotent char", !39, i64 0}
+!39 = !{!"Simple C/C++ TBAA"}
+!40 = !DILocation(line: 25, column: 11, scope: !25)
+!41 = !DILocation(line: 25, column: 3, scope: !25)
+!42 = !DILocation(line: 27, column: 1, scope: !25)
+!43 = !DILocation(line: 26, column: 3, scope: !25)
+!44 = !DISubprogram(name: "fn3", scope: !1, file: !1, line: 1, type: !45, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized, retainedNodes: !24)
+!45 = !DISubroutineType(types: !46)
+!46 = !{null, !47}
+!47 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !12, size: 64)
diff --git a/llvm/test/tools/llvm-dwarfdump/X86/Inputs/coverage.ll b/llvm/test/tools/llvm-dwarfdump/X86/Inputs/coverage.ll
new file mode 100644
index 0000000000000..a475c38a80de2
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfdump/X86/Inputs/coverage.ll
@@ -0,0 +1,170 @@
+; The source code of the test case:
+; extern void fn3(int *);
+; extern void fn2 (int);
+; __attribute__((noinline))
+; void
+; fn1 (int x, int y)
+; {
+; int u = x + y;
+; if (x > 1)
+; u += 1;
+; else
+; u += 2;
+; if (y > 4)
+; u += x;
+; int a = 7;
+; fn2 (a);
+; u --;
+; }
+
+; __attribute__((noinline))
+; int f()
+; {
+; int l, k;
+; fn3(&l);
+; fn3(&k);
+; fn1 (l, k);
+; return 0;
+; }
+
+; ModuleID = 'test.c'
+source_filename = "test.c"
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+; Function Attrs: noinline nounwind optnone uwtable
+define dso_local void @fn1(i32 noundef %0, i32 noundef %1) !dbg !10 {
+ %3 = alloca i32, align 4
+ %4 = alloca i32, align 4
+ %5 = alloca i32, align 4
+ %6 = alloca i32, align 4
+ store i32 %0, i32* %3, align 4
+ call void @llvm.dbg.declare(metadata i32* %3, metadata !15, metadata !DIExpression()), !dbg !16
+ store i32 %1, i32* %4, align 4
+ call void @llvm.dbg.declare(metadata i32* %4, metadata !17, metadata !DIExpression()), !dbg !18
+ call void @llvm.dbg.declare(metadata i32* %5, metadata !19, metadata !DIExpression()), !dbg !20
+ %7 = load i32, i32* %3, align 4, !dbg !21
+ %8 = load i32, i32* %4, align 4, !dbg !22
+ %9 = add nsw i32 %7, %8, !dbg !23
+ store i32 %9, i32* %5, align 4, !dbg !20
+ %10 = load i32, i32* %3, align 4, !dbg !24
+ %11 = icmp sgt i32 %10, 1, !dbg !26
+ br i1 %11, label %12, label %15, !dbg !27
+
+12: ; preds = %2
+ %13 = load i32, i32* %5, align 4, !dbg !28
+ %14 = add nsw i32 %13, 1, !dbg !28
+ store i32 %14, i32* %5, align 4, !dbg !28
+ br label %18, !dbg !29
+
+15: ; preds = %2
+ %16 = load i32, i32* %5, align 4, !dbg !30
+ %17 = add nsw i32 %16, 2, !dbg !30
+ store i32 %17, i32* %5, align 4, !dbg !30
+ br label %18
+
+18: ; preds = %15, %12
+ %19 = load i32, i32* %4, align 4, !dbg !31
+ %20 = icmp sgt i32 %19, 4, !dbg !33
+ br i1 %20, label %21, label %25, !dbg !34
+
+21: ; preds = %18
+ %22 = load i32, i32* %3, align 4, !dbg !35
+ %23 = load i32, i32* %5, align 4, !dbg !36
+ %24 = add nsw i32 %23, %22, !dbg !36
+ store i32 %24, i32* %5, align 4, !dbg !36
+ br label %25, !dbg !37
+
+25: ; preds = %21, %18
+ call void @llvm.dbg.declare(metadata i32* %6, metadata !38, metadata !DIExpression()), !dbg !39
+ store i32 7, i32* %6, align 4, !dbg !39
+ %26 = load i32, i32* %6, align 4, !dbg !40
+ call void @fn2(i32 noundef %26), !dbg !41
+ %27 = load i32, i32* %5, align 4, !dbg !42
+ %28 = add nsw i32 %27, -1, !dbg !42
+ store i32 %28, i32* %5, align 4, !dbg !42
+ ret void, !dbg !43
+}
+
+; Function Attrs: nofree nosync nounwind readnone speculatable willreturn
+declare void @llvm.dbg.declare(metadata, metadata, metadata)
+
+declare void @fn2(i32 noundef)
+
+; Function Attrs: noinline nounwind optnone uwtable
+define dso_local i32 @f() !dbg !44 {
+ %1 = alloca i32, align 4
+ %2 = alloca i32, align 4
+ call void @llvm.dbg.declare(metadata i32* %1, metadata !47, metadata !DIExpression()), !dbg !48
+ call void @llvm.dbg.declare(metadata i32* %2, metadata !49, metadata !DIExpression()), !dbg !50
+ call void @fn3(i32* noundef %1), !dbg !51
+ call void @fn3(i32* noundef %2), !dbg !52
+ %3 = load i32, i32* %1, align 4, !dbg !53
+ %4 = load i32, i32* %2, align 4, !dbg !54
+ call void @fn1(i32 noundef %3, i32 noundef %4), !dbg !55
+ ret i32 0, !dbg !56
+}
+
+declare void @fn3(i32* noundef)
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3, !4, !5, !6, !7, !8}
+!llvm.ident = !{!9}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 14.0.6", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+!1 = !DIFile(filename: "test.c", directory: "/")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !{i32 1, !"wchar_size", i32 4}
+!5 = !{i32 7, !"PIC Level", i32 2}
+!6 = !{i32 7, !"PIE Level", i32 2}
+!7 = !{i32 7, !"uwtable", i32 1}
+!8 = !{i32 7, !"frame-pointer", i32 2}
+!9 = !{!"clang version 14.0.6"}
+!10 = distinct !DISubprogram(name: "fn1", scope: !1, file: !1, line: 5, type: !11, scopeLine: 6, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !14)
+!11 = !DISubroutineType(types: !12)
+!12 = !{null, !13, !13}
+!13 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!14 = !{}
+!15 = !DILocalVariable(name: "x", arg: 1, scope: !10, file: !1, line: 5, type: !13)
+!16 = !DILocation(line: 5, column: 10, scope: !10)
+!17 = !DILocalVariable(name: "y", arg: 2, scope: !10, file: !1, line: 5, type: !13)
+!18 = !DILocation(line: 5, column: 17, scope: !10)
+!19 = !DILocalVariable(name: "u", scope: !10, file: !1, line: 7, type: !13)
+!20 = !DILocation(line: 7, column: 7, scope: !10)
+!21 = !DILocation(line: 7, column: 11, scope: !10)
+!22 = !DILocation(line: 7, column: 15, scope: !10)
+!23 = !DILocation(line: 7, column: 13, scope: !10)
+!24 = !DILocation(line: 8, column: 7, scope: !25)
+!25 = distinct !DILexicalBlock(scope: !10, file: !1, line: 8, column: 7)
+!26 = !DILocation(line: 8, column: 9, scope: !25)
+!27 = !DILocation(line: 8, column: 7, scope: !10)
+!28 = !DILocation(line: 9, column: 7, scope: !25)
+!29 = !DILocation(line: 9, column: 5, scope: !25)
+!30 = !DILocation(line: 11, column: 7, scope: !25)
+!31 = !DILocation(line: 12, column: 7, scope: !32)
+!32 = distinct !DILexicalBlock(scope: !10, file: !1, line: 12, column: 7)
+!33 = !DILocation(line: 12, column: 9, scope: !32)
+!34 = !DILocation(line: 12, column: 7, scope: !10)
+!35 = !DILocation(line: 13, column: 10, scope: !32)
+!36 = !DILocation(line: 13, column: 7, scope: !32)
+!37 = !DILocation(line: 13, column: 5, scope: !32)
+!38 = !DILocalVariable(name: "a", scope: !10, file: !1, line: 14, type: !13)
+!39 = !DILocation(line: 14, column: 7, scope: !10)
+!40 = !DILocation(line: 15, column: 8, scope: !10)
+!41 = !DILocation(line: 15, column: 3, scope: !10)
+!42 = !DILocation(line: 16, column: 5, scope: !10)
+!43 = !DILocation(line: 17, column: 1, scope: !10)
+!44 = distinct !DISubprogram(name: "f", scope: !1, file: !1, line: 20, type: !45, scopeLine: 21, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !14)
+!45 = !DISubroutineType(types: !46)
+!46 = !{!13}
+!47 = !DILocalVariable(name: "l", scope: !44, file: !1, line: 22, type: !13)
+!48 = !DILocation(line: 22, column: 7, scope: !44)
+!49 = !DILocalVariable(name: "k", scope: !44, file: !1, line: 22, type: !13)
+!50 = !DILocation(line: 22, column: 10, scope: !44)
+!51 = !DILocation(line: 23, column: 3, scope: !44)
+!52 = !DILocation(line: 24, column: 3, scope: !44)
+!53 = !DILocation(line: 25, column: 8, scope: !44)
+!54 = !DILocation(line: 25, column: 11, scope: !44)
+!55 = !DILocation(line: 25, column: 3, scope: !44)
+!56 = !DILocation(line: 26, column: 3, scope: !44)
diff --git a/llvm/test/tools/llvm-dwarfdump/X86/coverage.test b/llvm/test/tools/llvm-dwarfdump/X86/coverage.test
new file mode 100644
index 0000000000000..1f4471b07e5f3
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfdump/X86/coverage.test
@@ -0,0 +1,24 @@
+; RUN: llc %S/Inputs/coverage.ll -o %t.o -filetype=obj
+; RUN: llc %S/Inputs/coverage-opt.ll -o %t-opt.o -filetype=obj
+
+; RUN: llvm-dwarfdump --show-variable-coverage %t.o | FileCheck %s
+
+; CHECK: Variable coverage statistics:
+; CHECK-NEXT: Function InlChain Variable Decl LinesCovered
+; CHECK-NEXT: f k test.c:22 5
+; CHECK-NEXT: f l test.c:22 5
+; CHECK-NEXT: fn1 a test.c:14 11
+; CHECK-NEXT: fn1 u test.c:7 11
+; CHECK-NEXT: fn1 x test.c:5 11
+; CHECK-NEXT: fn1 y test.c:5 11
+
+; RUN: llvm-dwarfdump --show-variable-coverage --combine-instances %t-opt.o | FileCheck %s --check-prefix=COMBINE
+
+; COMBINE: Variable coverage statistics:
+; COMBINE-NEXT: Function InstanceCount Variable Decl LinesCovered
+; COMBINE-NEXT: f 1 k test.c:22 2
+; COMBINE-NEXT: f 1 l test.c:22 3
+; COMBINE-NEXT: fn1 1 a test.c:14 1
+; COMBINE-NEXT: fn1 1 u test.c:7 1
+; COMBINE-NEXT: fn1 1 x test.c:5 1
+; COMBINE-NEXT: fn1 1 y test.c:5 1
diff --git a/llvm/tools/llvm-dwarfdump/CMakeLists.txt b/llvm/tools/llvm-dwarfdump/CMakeLists.txt
index 7a0adf32e938c..1ed3f4901c4fc 100644
--- a/llvm/tools/llvm-dwarfdump/CMakeLists.txt
+++ b/llvm/tools/llvm-dwarfdump/CMakeLists.txt
@@ -11,6 +11,7 @@ set(LLVM_LINK_COMPONENTS
)
add_llvm_tool(llvm-dwarfdump
+ Coverage.cpp
SectionSizes.cpp
Statistics.cpp
llvm-dwarfdump.cpp
diff --git a/llvm/tools/llvm-dwarfdump/Coverage.cpp b/llvm/tools/llvm-dwarfdump/Coverage.cpp
new file mode 100644
index 0000000000000..e74b31900a471
--- /dev/null
+++ b/llvm/tools/llvm-dwarfdump/Coverage.cpp
@@ -0,0 +1,244 @@
+//===-- Coverage.cpp - Debug info coverage metrics ------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm-dwarfdump.h"
+#include "llvm/BinaryFormat/Dwarf.h"
+#include "llvm/DebugInfo/DIContext.h"
+#include "llvm/DebugInfo/DWARF/DWARFAcceleratorTable.h"
+#include "llvm/DebugInfo/DWARF/DWARFCompileUnit.h"
+#include "llvm/DebugInfo/DWARF/DWARFContext.h"
+#include "llvm/IR/CFG.h"
+#include "llvm/IR/DebugInfoMetadata.h"
+#include "llvm/IR/DebugProgramInstruction.h"
+#include "llvm/IR/Instructions.h"
+#include "llvm/IR/Module.h"
+#include "llvm/IRReader/IRReader.h"
+#include "llvm/Object/ObjectFile.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/SourceMgr.h"
+#include <set>
+
+using namespace llvm;
+using namespace llvm::dwarf;
+using namespace llvm::object;
+
+typedef std::pair<std::string, std::string> StringPair;
+
+static std::optional<std::set<std::pair<uint16_t, uint32_t>>>
+computeVariableCoverage(DWARFContext &DICtx, DWARFDie DIE,
+ const DWARFDebugLine::LineTable *const LineTable) {
+ auto addLines = [](const DWARFDebugLine::LineTable *LineTable,
+ std::set<std::pair<uint16_t, uint32_t>> &Lines,
+ DWARFAddressRange Range,
+ std::map<std::string, uint16_t, std::less<>> &FileNames) {
+ std::vector<uint32_t> Rows;
+ if (LineTable->lookupAddressRange({Range.LowPC, Range.SectionIndex},
+ Range.HighPC - Range.LowPC, Rows)) {
+ for (const auto &RowI : Rows) {
+ const auto Row = LineTable->Rows[RowI];
+ if (Row.Address.Address < Range.LowPC)
+ continue;
+ const auto FileIndex = Row.File;
+
+ if (!any_of(FileNames,
+ [FileIndex](auto &FN) { return FN.second == FileIndex; })) {
+ std::string Name;
+ LineTable->getFileNameByIndex(
+ FileIndex, "",
+ DILineInfoSpecifier::FileLineInfoKind::RelativeFilePath, Name);
+ FileNames.emplace(Name, FileIndex);
+ }
+
+ const auto Line = Row.Line;
+ if (Line) // ignore zero lines
+ Lines.insert({FileIndex, Line});
+ }
+ }
+ };
+
+ std::map<std::string, uint16_t, std::less<>> FileNames;
+
+ auto Locations = DIE.getLocations(DW_AT_location);
+ std::optional<std::set<std::pair<uint16_t, uint32_t>>> Lines;
+ if (Locations) {
+ for (const auto &L : Locations.get()) {
+ if (L.Range) {
+ if (!Lines)
+ Lines = {{}};
+ addLines(LineTable, *Lines, L.Range.value(), FileNames);
+ }
+ }
+ } else {
+ consumeError(Locations.takeError());
+ }
+
+ auto Ranges = DIE.getParent().getAddressRanges();
+ std::optional<std::set<std::pair<uint16_t, uint32_t>>> ParentLines;
+ if (Ranges) {
+ ParentLines = {{}};
+ for (const auto &R : Ranges.get())
+ addLines(LineTable, *ParentLines, R, FileNames);
+ } else {
+ consumeError(Ranges.takeError());
+ }
+
+ if (!Lines && ParentLines) {
+ Lines = ParentLines;
+ } else if (ParentLines) {
+ std::set<std::pair<uint16_t, uint32_t>> Intersection;
+ std::set_intersection(Lines->begin(), Lines->end(), ParentLines->begin(),
+ ParentLines->end(),
+ std::inserter(Intersection, Intersection.begin()));
+ Lines = Intersection;
+ }
+
+ return Lines;
+}
+
+static const SmallVector<DWARFDie> getParentSubroutines(DWARFDie DIE) {
+ SmallVector<DWARFDie> Parents;
+ DWARFDie Parent = DIE;
+ do
+ if (Parent.getTag() == DW_TAG_subprogram ||
+ Parent.getTag() == DW_TAG_inlined_subroutine)
+ Parents.push_back(Parent);
+ while ((Parent = Parent.getParent()));
+ return Parents;
+}
+
+struct VarKey {
+ const char *const SubprogramName;
+ const char *const Name;
+ std::string DeclFile;
+ unsigned long DeclLine;
+
+ bool operator==(const VarKey &Other) const {
+ return DeclLine == Other.DeclLine &&
+ !strcmp(SubprogramName, Other.SubprogramName) &&
+ !strcmp(Name, Other.Name) && !DeclFile.compare(Other.DeclFile);
+ }
+
+ bool operator<(const VarKey &Other) const {
+ int A = strcmp(SubprogramName, Other.SubprogramName);
+ if (A)
+ return A < 0;
+ int B = strcmp(Name, Other.Name);
+ if (B)
+ return B < 0;
+ int C = DeclFile.compare(Other.DeclFile);
+ if (C)
+ return C < 0;
+ return DeclLine < Other.DeclLine;
+ }
+};
+
+struct VarCoverage {
+ SmallVector<DWARFDie> Parents;
+ size_t Cov;
+ size_t Instances;
+};
+
+typedef std::multimap<VarKey, VarCoverage, std::less<>> VarMap;
+
+static std::optional<const VarKey> getVarKey(DWARFDie Die, DWARFDie Parent) {
+ const auto *const DieName = Die.getName(DINameKind::LinkageName);
+ const auto DieFile =
+ Die.getDeclFile(DILineInfoSpecifier::FileLineInfoKind::RelativeFilePath);
+ const auto *const ParentName = Parent.getName(DINameKind::LinkageName);
+ if (!DieName || !ParentName)
+ return std::nullopt;
+ return VarKey{ParentName, DieName, DieFile, Die.getDeclLine()};
+}
+
+static void displayParents(SmallVector<DWARFDie> Parents, raw_ostream &OS) {
+ bool First = true;
+ for (const auto Parent : Parents) {
+ if (auto FormValue = Parent.find(DW_AT_call_file)) {
+ if (auto OptString = FormValue->getAsFile(
+ DILineInfoSpecifier::FileLineInfoKind::RelativeFilePath)) {
+ if (First)
+ First = false;
+ else
+ OS << ", ";
+ OS << *OptString << ":" << toUnsigned(Parent.find(DW_AT_call_line), 0);
+ }
+ }
+ }
+}
+
+static void displayVariableCoverage(const VarKey &Key, const VarCoverage &Var,
+ bool CombineInstances, raw_ostream &OS) {
+ WithColor(OS, HighlightColor::String) << Key.SubprogramName;
+ OS << "\t";
+ if (CombineInstances)
+ OS << Var.Instances;
+ else if (Var.Parents.size())
+ displayParents(Var.Parents, OS);
+ OS << "\t";
+ WithColor(OS, HighlightColor::String) << Key.Name;
+ OS << "\t" << Key.DeclFile << ":" << Key.DeclLine;
+ OS << "\t" << format("%.3g", ((float)Var.Cov / Var.Instances));
+ OS << "\n";
+}
+
+bool dwarfdump::showVariableCoverage(ObjectFile &Obj, DWARFContext &DICtx,
+ bool CombineInstances, raw_ostream &OS) {
+ VarMap Vars;
+
+ WithColor(errs(), HighlightColor::Remark) << "Processing DIEs...\n";
+
+ for (const auto &U : DICtx.info_section_units()) {
+ const auto *const LT = DICtx.getLineTableForUnit(U.get());
+ for (const auto &Entry : U->dies()) {
+ DWARFDie Die = {U.get(), &Entry};
+ if (Die.getTag() != DW_TAG_variable &&
+ Die.getTag() != DW_TAG_formal_parameter)
+ continue;
+
+ const auto Parents = getParentSubroutines(Die);
+ if (!Parents.size())
+ continue;
+ const auto Parent = Parents.front();
+ auto Key = getVarKey(Die, Parent);
+ if (!Key)
+ continue;
+
+ const auto Cov = computeVariableCoverage(DICtx, Die, LT);
+ if (!Cov || Cov->empty())
+ continue;
+
+ VarCoverage VarCov = {Parents, Cov->size(), 1};
+
+ Vars.insert({*Key, VarCov});
+ }
+ }
+
+ std::pair<VarMap::iterator, VarMap::iterator> Range;
+
+ OS << "\nVariable coverage statistics:\nFunction\t"
+ << (CombineInstances ? "InstanceCount" : "InlChain")
+ << "\tVariable\tDecl\tLinesCovered\n";
+
+ if (CombineInstances) {
+ for (auto FirstVar = Vars.begin(); FirstVar != Vars.end();
+ FirstVar = Range.second) {
+ Range = Vars.equal_range(FirstVar->first);
+ VarCoverage CombinedCov = {{}, 0, 0};
+ for (auto Var = Range.first; Var != Range.second; ++Var) {
+ ++CombinedCov.Instances;
+ CombinedCov.Cov += Var->second.Cov;
+ }
+ displayVariableCoverage(FirstVar->first, CombinedCov, true, OS);
+ }
+ } else {
+ for (auto Var : Vars)
+ displayVariableCoverage(Var.first, Var.second, false, OS);
+ }
+
+ return true;
+}
diff --git a/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.cpp b/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.cpp
index 6f120f93700f6..d4c60593fd667 100644
--- a/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.cpp
+++ b/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.cpp
@@ -334,6 +334,16 @@ static opt<bool> Verbose("verbose",
cat(DwarfDumpCategory));
static alias VerboseAlias("v", desc("Alias for --verbose."), aliasopt(Verbose),
cat(DwarfDumpCategory), cl::NotHidden);
+static opt<bool>
+ ShowVariableCoverage("show-variable-coverage",
+ desc("Show per-variable coverage metrics."),
+ cat(DwarfDumpCategory));
+static opt<bool> CombineInstances(
+ "combine-instances",
+ desc(
+ "Use with --show-variable-coverage to average variable coverage across "
+ "inlined subroutine instances instead of printing them separately."),
+ cat(DwarfDumpCategory));
static cl::extrahelp
HelpResponse("\nPass @FILE as argument to read options from FILE.\n");
} // namespace
@@ -905,7 +915,7 @@ int main(int argc, char **argv) {
DumpType |= DIDT_UUID;
if (DumpAll)
DumpType = DIDT_All;
- if (DumpType == DIDT_Null) {
+ if (DumpType == DIDT_Null && !ShowVariableCoverage) {
if (Verbose || Verify)
DumpType = DIDT_All;
else
@@ -957,5 +967,14 @@ int main(int argc, char **argv) {
Success &= handleFile(Object, dumpObjectFile, OutputFile.os());
}
+ if (ShowVariableCoverage) {
+ auto showCoverage = [&](ObjectFile &Obj, DWARFContext &DICtx,
+ const Twine &Filename, raw_ostream &OS) {
+ return showVariableCoverage(Obj, DICtx, CombineInstances, OS);
+ };
+ for (StringRef Object : Objects)
+ Success &= handleFile(Object, showCoverage, OutputFile.os());
+ }
+
return Success ? EXIT_SUCCESS : EXIT_FAILURE;
}
diff --git a/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.h b/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.h
index cf7da56c91f89..ff6d91f12009e 100644
--- a/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.h
+++ b/llvm/tools/llvm-dwarfdump/llvm-dwarfdump.h
@@ -39,6 +39,8 @@ bool collectStatsForObjectFile(object::ObjectFile &Obj, DWARFContext &DICtx,
bool collectObjectSectionSizes(object::ObjectFile &Obj, DWARFContext &DICtx,
const Twine &Filename, raw_ostream &OS);
+bool showVariableCoverage(object::ObjectFile &Obj, DWARFContext &DICtx,
+ bool CombineInstances, raw_ostream &OS);
} // namespace dwarfdump
} // namespace llvm
>From 2681aa5a8dc494e152e7cc6f6586d95c35c52302 Mon Sep 17 00:00:00 2001
From: unexpectedlydefined <unexpectedlydefined at gmail.com>
Date: Wed, 26 Nov 2025 12:49:28 +0000
Subject: [PATCH 2/3] fixup! Add line table coverage metrics
Fix docs
---
llvm/docs/CommandGuide/llvm-dwarfdump.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/docs/CommandGuide/llvm-dwarfdump.rst b/llvm/docs/CommandGuide/llvm-dwarfdump.rst
index c3af617d8b6c2..82f6aab4e56d1 100644
--- a/llvm/docs/CommandGuide/llvm-dwarfdump.rst
+++ b/llvm/docs/CommandGuide/llvm-dwarfdump.rst
@@ -257,7 +257,7 @@ The following is generated if there are no errors reported::
.. _variable-coverage-format:
FORMAT OF VARIABLE COVERAGE OUTPUT
----------------------------
+----------------------------------
The :option:`--show-variable-coverage` option differs from
:option:`--statistics` by printing per-variable debug info coverage metrics
>From b58fcaca42355f2505081c56a318ae40dfc397be Mon Sep 17 00:00:00 2001
From: unexpectedlydefined <unexpectedlydefined at gmail.com>
Date: Wed, 26 Nov 2025 13:04:14 +0000
Subject: [PATCH 3/3] fixup! Add line table coverage metrics
---
.../tools/llvm-dwarfdump/X86/coverage.test | 40 +++++++++----------
1 file changed, 20 insertions(+), 20 deletions(-)
diff --git a/llvm/test/tools/llvm-dwarfdump/X86/coverage.test b/llvm/test/tools/llvm-dwarfdump/X86/coverage.test
index 1f4471b07e5f3..8b22d10c668bd 100644
--- a/llvm/test/tools/llvm-dwarfdump/X86/coverage.test
+++ b/llvm/test/tools/llvm-dwarfdump/X86/coverage.test
@@ -1,24 +1,24 @@
-; RUN: llc %S/Inputs/coverage.ll -o %t.o -filetype=obj
-; RUN: llc %S/Inputs/coverage-opt.ll -o %t-opt.o -filetype=obj
+RUN: llc %S/Inputs/coverage.ll -o %t.o -filetype=obj
+RUN: llc %S/Inputs/coverage-opt.ll -o %t-opt.o -filetype=obj
-; RUN: llvm-dwarfdump --show-variable-coverage %t.o | FileCheck %s
+RUN: llvm-dwarfdump --show-variable-coverage %t.o | FileCheck %s
-; CHECK: Variable coverage statistics:
-; CHECK-NEXT: Function InlChain Variable Decl LinesCovered
-; CHECK-NEXT: f k test.c:22 5
-; CHECK-NEXT: f l test.c:22 5
-; CHECK-NEXT: fn1 a test.c:14 11
-; CHECK-NEXT: fn1 u test.c:7 11
-; CHECK-NEXT: fn1 x test.c:5 11
-; CHECK-NEXT: fn1 y test.c:5 11
+CHECK: Variable coverage statistics:
+CHECK-NEXT: Function InlChain Variable Decl LinesCovered
+CHECK-NEXT: f k test.c:22 5
+CHECK-NEXT: f l test.c:22 5
+CHECK-NEXT: fn1 a test.c:14 11
+CHECK-NEXT: fn1 u test.c:7 11
+CHECK-NEXT: fn1 x test.c:5 11
+CHECK-NEXT: fn1 y test.c:5 11
-; RUN: llvm-dwarfdump --show-variable-coverage --combine-instances %t-opt.o | FileCheck %s --check-prefix=COMBINE
+RUN: llvm-dwarfdump --show-variable-coverage --combine-instances %t-opt.o | FileCheck %s --check-prefix=COMBINE
-; COMBINE: Variable coverage statistics:
-; COMBINE-NEXT: Function InstanceCount Variable Decl LinesCovered
-; COMBINE-NEXT: f 1 k test.c:22 2
-; COMBINE-NEXT: f 1 l test.c:22 3
-; COMBINE-NEXT: fn1 1 a test.c:14 1
-; COMBINE-NEXT: fn1 1 u test.c:7 1
-; COMBINE-NEXT: fn1 1 x test.c:5 1
-; COMBINE-NEXT: fn1 1 y test.c:5 1
+COMBINE: Variable coverage statistics:
+COMBINE-NEXT: Function InstanceCount Variable Decl LinesCovered
+COMBINE-NEXT: f 1 k test.c:22 2
+COMBINE-NEXT: f 1 l test.c:22 3
+COMBINE-NEXT: fn1 1 a test.c:14 1
+COMBINE-NEXT: fn1 1 u test.c:7 1
+COMBINE-NEXT: fn1 1 x test.c:5 1
+COMBINE-NEXT: fn1 1 y test.c:5 1
More information about the llvm-commits
mailing list