Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions llvm/docs/CommandGuide/llvm-dwarfdump.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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-inline-variable-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>`.
Expand Down Expand Up @@ -243,6 +254,34 @@ 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-inline-variable-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-inline-variable-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
-----------

Expand Down
121 changes: 121 additions & 0 deletions llvm/test/tools/llvm-dwarfdump/X86/Inputs/coverage-opt.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
; 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-i128:128-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 %0, i32 %1) local_unnamed_addr !dbg !10 {
#dbg_value(i32 poison, !15, !DIExpression(), !19)
#dbg_value(i32 poison, !16, !DIExpression(), !19)
#dbg_value(!DIArgList(i32 poison, i32 poison), !17, !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 1, DW_OP_plus, DW_OP_stack_value), !19)
#dbg_value(i32 poison, !17, !DIExpression(), !19)
#dbg_value(i32 poison, !17, !DIExpression(), !19)
#dbg_value(i32 7, !18, !DIExpression(), !19)
tail call void @fn2(i32 noundef 7) #3, !dbg !20
#dbg_value(i32 poison, !17, !DIExpression(DW_OP_constu, 1, DW_OP_minus, DW_OP_stack_value), !19)
Comment on lines +37 to +44
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like all of the non-const arguments became poison, rather than just the ones that were previously undef. It's good to test at least one variable that's completely dead, so I'd recommend keeping at least one "fully dead" variable.

ret void, !dbg !21
}

; Function Attrs: mustprogress nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture)

declare !dbg !22 void @fn2(i32 noundef) local_unnamed_addr

; Function Attrs: mustprogress nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture)

; Function Attrs: noinline nounwind uwtable
define dso_local noundef i32 @f() local_unnamed_addr !dbg !25 {
%1 = alloca i32, align 4, !DIAssignID !31
#dbg_assign(i1 poison, !29, !DIExpression(), !31, ptr %1, !DIExpression(), !32)
%2 = alloca i32, align 4, !DIAssignID !33
#dbg_assign(i1 poison, !30, !DIExpression(), !33, ptr %2, !DIExpression(), !32)
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %1) #3, !dbg !34
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %2) #3, !dbg !34
call void @fn3(ptr noundef nonnull %1) #3, !dbg !35
call void @fn3(ptr noundef nonnull %2) #3, !dbg !36
call void @fn1(i32 poison, i32 poison), !dbg !37
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %2) #3, !dbg !38
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %1) #3, !dbg !38
ret i32 0, !dbg !39
}

declare !dbg !40 void @fn3(ptr noundef) local_unnamed_addr

!llvm.dbg.cu = !{!0}
!llvm.module.flags = !{!2, !3, !4, !5, !6, !7, !8}
!llvm.ident = !{!9}

!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang version 19.1.7", 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 8, !"PIC Level", i32 2}
!6 = !{i32 7, !"PIE Level", i32 2}
!7 = !{i32 7, !"uwtable", i32 2}
!8 = !{i32 7, !"debug-info-assignment-tracking", i1 true}
!9 = !{!"clang version 19.1.7"}
!10 = distinct !DISubprogram(name: "fn1", scope: !1, file: !1, line: 5, type: !11, scopeLine: 6, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !14)
!11 = !DISubroutineType(types: !12)
!12 = !{null, !13, !13}
!13 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
!14 = !{!15, !16, !17, !18}
!15 = !DILocalVariable(name: "x", arg: 1, scope: !10, file: !1, line: 5, type: !13)
!16 = !DILocalVariable(name: "y", arg: 2, scope: !10, file: !1, line: 5, type: !13)
!17 = !DILocalVariable(name: "u", scope: !10, file: !1, line: 7, type: !13)
!18 = !DILocalVariable(name: "a", scope: !10, file: !1, line: 14, type: !13)
!19 = !DILocation(line: 0, scope: !10)
!20 = !DILocation(line: 15, column: 3, scope: !10)
!21 = !DILocation(line: 17, column: 1, scope: !10)
!22 = !DISubprogram(name: "fn2", scope: !1, file: !1, line: 2, type: !23, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
!23 = !DISubroutineType(types: !24)
!24 = !{null, !13}
!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 = !{!13}
!28 = !{!29, !30}
!29 = !DILocalVariable(name: "l", scope: !25, file: !1, line: 22, type: !13)
!30 = !DILocalVariable(name: "k", scope: !25, file: !1, line: 22, type: !13)
!31 = distinct !DIAssignID()
!32 = !DILocation(line: 0, scope: !25)
!33 = distinct !DIAssignID()
!34 = !DILocation(line: 22, column: 3, scope: !25)
!35 = !DILocation(line: 23, column: 3, scope: !25)
!36 = !DILocation(line: 24, column: 3, scope: !25)
!37 = !DILocation(line: 25, column: 3, scope: !25)
!38 = !DILocation(line: 27, column: 1, scope: !25)
!39 = !DILocation(line: 26, column: 3, scope: !25)
!40 = !DISubprogram(name: "fn3", scope: !1, file: !1, line: 1, type: !41, flags: DIFlagPrototyped, spFlags: DISPFlagOptimized)
!41 = !DISubroutineType(types: !42)
!42 = !{null, !43}
!43 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !13, size: 64)
167 changes: 167 additions & 0 deletions llvm/test/tools/llvm-dwarfdump/X86/Inputs/coverage.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
; 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-i128:128-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, ptr %3, align 4
#dbg_declare(ptr %3, !15, !DIExpression(), !16)
store i32 %1, ptr %4, align 4
#dbg_declare(ptr %4, !17, !DIExpression(), !18)
#dbg_declare(ptr %5, !19, !DIExpression(), !20)
%7 = load i32, ptr %3, align 4, !dbg !21
%8 = load i32, ptr %4, align 4, !dbg !22
%9 = add nsw i32 %7, %8, !dbg !23
store i32 %9, ptr %5, align 4, !dbg !20
%10 = load i32, ptr %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, ptr %5, align 4, !dbg !28
%14 = add nsw i32 %13, 1, !dbg !28
store i32 %14, ptr %5, align 4, !dbg !28
br label %18, !dbg !29

15: ; preds = %2
%16 = load i32, ptr %5, align 4, !dbg !30
%17 = add nsw i32 %16, 2, !dbg !30
store i32 %17, ptr %5, align 4, !dbg !30
br label %18

18: ; preds = %15, %12
%19 = load i32, ptr %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, ptr %3, align 4, !dbg !35
%23 = load i32, ptr %5, align 4, !dbg !36
%24 = add nsw i32 %23, %22, !dbg !36
store i32 %24, ptr %5, align 4, !dbg !36
br label %25, !dbg !37

25: ; preds = %21, %18
#dbg_declare(ptr %6, !38, !DIExpression(), !39)
store i32 7, ptr %6, align 4, !dbg !39
%26 = load i32, ptr %6, align 4, !dbg !40
call void @fn2(i32 noundef %26), !dbg !41
%27 = load i32, ptr %5, align 4, !dbg !42
%28 = add nsw i32 %27, -1, !dbg !42
store i32 %28, ptr %5, align 4, !dbg !42
ret void, !dbg !43
}

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
#dbg_declare(ptr %1, !47, !DIExpression(), !48)
#dbg_declare(ptr %2, !49, !DIExpression(), !50)
call void @fn3(ptr noundef %1), !dbg !51
call void @fn3(ptr noundef %2), !dbg !52
%3 = load i32, ptr %1, align 4, !dbg !53
%4 = load i32, ptr %2, align 4, !dbg !54
call void @fn1(i32 noundef %3, i32 noundef %4), !dbg !55
ret i32 0, !dbg !56
}

declare void @fn3(ptr noundef)

!llvm.dbg.cu = !{!0}
!llvm.module.flags = !{!2, !3, !4, !5, !6, !7, !8}
!llvm.ident = !{!9}

!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang version 19.1.7", 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 8, !"PIC Level", i32 2}
!6 = !{i32 7, !"PIE Level", i32 2}
!7 = !{i32 7, !"uwtable", i32 2}
!8 = !{i32 7, !"frame-pointer", i32 2}
!9 = !{!"clang version 19.1.7"}
!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)
24 changes: 24 additions & 0 deletions llvm/test/tools/llvm-dwarfdump/X86/coverage.test
Original file line number Diff line number Diff line change
@@ -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
Comment on lines +1 to +2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand it, both of these input files will be used by other tests independent of this one in a future patch? Just noting this for review, as normally these tests would each be self-contained, but in this case they'll also be used as inputs for multiple tests, including at least one that uses both the files as input to one command - is that correct?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - subsequent patches will add several more llvm-dwarfdump invocations using different argument combinations, including some that use both files as input.


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-inline-variable-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 5
COMBINE-NEXT: f 1 l test.c:22 5
COMBINE-NEXT: fn1 1 a test.c:14 0
COMBINE-NEXT: fn1 1 u test.c:7 0
COMBINE-NEXT: fn1 1 x test.c:5 0
COMBINE-NEXT: fn1 1 y test.c:5 0
Comment on lines +19 to +24
Copy link
Contributor

@SLTozer SLTozer Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Results here look a little odd; all the fn1 variables other than a were made fully undef in the most recent change, but even a has a 0 line coverage here even though it should have full coverage in fn1; is this happening because fn1 is ending up with 0 scope lines, or is there some problem with the logic for finding the scope lines? (Also k and l have changed results as well; possibly the fn1 results are being miscounted as being in f, and thus all the scope lines for fn1 are being counted for f variables? Something to look into either way).

1 change: 1 addition & 0 deletions llvm/tools/llvm-dwarfdump/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ set(LLVM_LINK_COMPONENTS
)

add_llvm_tool(llvm-dwarfdump
Coverage.cpp
SectionSizes.cpp
Statistics.cpp
llvm-dwarfdump.cpp
Expand Down
Loading