[llvm-objdump] Print method name from debug info in disassembly output.

Summary:
GNU objdump prints the method name in disassembly output, and upon further investigation this seems to come from debug info, not the symbol table.

Some additional refactoring is necessary to make this work even when the line number is 0/the filename is unknown. The added test case includes a note for this scenario.

See http://llvm.org/PR41341 for more info.

Reviewers: dblaikie, MaskRay, jhenderson

Reviewed By: MaskRay

Subscribers: ormris, jvesely, aprantl, kerbowa, llvm-commits

Tags: #llvm

Differential Revision: https://reviews.llvm.org/D74507
diff --git a/llvm/test/tools/llvm-objdump/AMDGPU/source-lines.ll b/llvm/test/tools/llvm-objdump/AMDGPU/source-lines.ll
index 2e31f66..2c59008 100644
--- a/llvm/test/tools/llvm-objdump/AMDGPU/source-lines.ll
+++ b/llvm/test/tools/llvm-objdump/AMDGPU/source-lines.ll
@@ -5,6 +5,7 @@
 
 ; Prologue.
 ; LINE:      source_lines_test:
+; LINE-NEXT: ; source_lines_test():
 ; LINE-NEXT: ; {{.*}}source-lines.cl:1
 ; Kernel.
 ; LINE: v_mov_b32_e32 v{{[0-9]+}}, 0x777
diff --git a/llvm/test/tools/llvm-objdump/Hexagon/source-interleave-hexagon.ll b/llvm/test/tools/llvm-objdump/Hexagon/source-interleave-hexagon.ll
index 35ca706..69058f4 100644
--- a/llvm/test/tools/llvm-objdump/Hexagon/source-interleave-hexagon.ll
+++ b/llvm/test/tools/llvm-objdump/Hexagon/source-interleave-hexagon.ll
@@ -66,6 +66,7 @@
 !22 = !DILocation(line: 8, column: 13, scope: !14)
 !23 = !DILocation(line: 8, column: 3, scope: !14)
 ; LINES: main:
+; LINES-NEXT: main():
 ; LINES-NEXT: SRC_COMPDIR/source-interleave-hexagon.c:6
 
 ; SOURCE: main:
diff --git a/llvm/test/tools/llvm-objdump/X86/source-interleave-function-from-debug.test b/llvm/test/tools/llvm-objdump/X86/source-interleave-function-from-debug.test
new file mode 100644
index 0000000..bbf38f89
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/X86/source-interleave-function-from-debug.test
@@ -0,0 +1,108 @@
+;; Verify that llvm-objdump -l also prints the function name in disassembly
+;; output, getting it from the debug info.
+
+; RUN: llc < %s -o %t.o -filetype=obj -mtriple=x86_64-unknown-linux-gnu
+; RUN: llvm-objdump -dl %t.o | FileCheck %s --check-prefixes=CHECK,CHECK-NO-DEMANGLE
+
+; RUN: llc < %s -o %t.o -filetype=obj -mtriple=x86_64-unknown-linux-gnu
+; RUN: llvm-objdump -dlC %t.o | FileCheck %s --check-prefixes=CHECK,CHECK-DEMANGLE
+
+; CHECK:      0000000000000000 foo:
+; CHECK-NEXT: ; foo():
+; CHECK-NEXT: ; /tmp/src.cc:1
+; CHECK-NEXT:        0: b8 05 00 00 00                movl    $5, %eax
+; CHECK-NEXT:        5: c3                            retq
+
+; CHECK-NO-DEMANGLE:      0000000000000010 _ZN3xyz3barEv:
+; CHECK-NO-DEMANGLE-NEXT: ; _ZN3xyz3barEv():
+; CHECK-DEMANGLE:         0000000000000010 xyz::bar():
+; CHECK-DEMANGLE-NEXT:    ; xyz::bar():
+
+; CHECK-NEXT: ; /tmp/src.cc:3
+; CHECK-NEXT:       10: b8 0a 00 00 00                movl    $10, %eax
+; CHECK-NEXT:       15: c3                            retq
+
+; CHECK-NO-DEMANGLE:      0000000000000020 _ZN3xyz3bazEv:
+; CHECK-NO-DEMANGLE-NEXT: ; _ZN3xyz3bazEv():
+; CHECK-DEMANGLE:         0000000000000020 xyz::baz():
+; CHECK-DEMANGLE-NEXT:    ; xyz::baz():
+
+; CHECK-NEXT: ; /tmp/src.cc:3
+; CHECK-NEXT:       20: b8 14 00 00 00                movl    $20, %eax
+; CHECK-NEXT:       25: c3                            retq
+
+;; When symbol information is missing, we can get function names from debug
+;; info. The IR is intentionally doctored to have different names in debug info
+;; for the test case here.
+; RUN: llvm-strip %t.o -N foo -N _ZN3xyz3barEv -N _ZN3xyz3bazEv -o %t-stripped.o
+; RUN: llvm-objdump -dlC %t-stripped.o | FileCheck %s --check-prefix=STRIPPED
+
+; STRIPPED:      0000000000000000 .text:
+; STRIPPED-NEXT: ; Function1():
+; STRIPPED-NEXT: ; /tmp/src.cc:1
+; STRIPPED-NEXT:        0: b8 05 00 00 00                movl    $5, %eax
+; STRIPPED-NEXT:        5: c3                            retq
+
+; STRIPPED:      ; xyz::bar():
+; STRIPPED-NEXT: ; /tmp/src.cc:3
+; STRIPPED-NEXT:       10: b8 0a 00 00 00                movl    $10, %eax
+; STRIPPED-NEXT:       15: c3                            retq
+
+; STRIPPED:      ; xyz::baz():
+; STRIPPED-NEXT: ; /tmp/src.cc:3
+; STRIPPED-NEXT:       20: b8 14 00 00 00                movl    $20, %eax
+; STRIPPED-NEXT:       25: c3                            retq
+
+;; IR adapted from:
+;; $ cat /tmp/src.cc
+;; extern "C" int foo() { return 5; };
+;; namespace xyz {
+;; int bar() { return 10; } int baz() { return 20; }
+;; } // namespace xyz
+;; $ clang++ -O -g -c /tmp/src.cc -S -emit-llvm
+;; Note: bar() and baz() intentionally written on the same line.
+
+; ModuleID = '/tmp/src.cc'
+source_filename = "/tmp/src.cc"
+target triple = "x86_64-unknown-linux-gnu"
+
+define dso_local i32 @foo() #0 !dbg !7 {
+entry:
+  ret i32 5, !dbg !12
+}
+
+define dso_local i32 @_ZN3xyz3barEv() #0 !dbg !13 {
+entry:
+  ret i32 10, !dbg !15
+}
+
+define dso_local i32 @_ZN3xyz3bazEv() #0 !dbg !16 {
+entry:
+  ret i32 20, !dbg !17
+}
+
+attributes #0 = { "frame-pointer"="none" }
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3, !4, !5}
+!llvm.ident = !{!6}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang trunk", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, splitDebugInlining: false, nameTableKind: None)
+; Note: <invalid> triggers a bad DILineInfo. We still print "Function1()".
+!1 = !DIFile(filename: "<invalid>", directory: "")
+!2 = !{}
+!3 = !{i32 7, !"Dwarf Version", i32 4}
+!4 = !{i32 2, !"Debug Info Version", i32 3}
+!5 = !{i32 1, !"wchar_size", i32 4}
+!6 = !{!"clang trunk)"}
+!7 = distinct !DISubprogram(name: "Function1", scope: !8, file: !8, line: 1, type: !9, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !2)
+!8 = !DIFile(filename: "/tmp/src.cc", directory: "")
+!9 = !DISubroutineType(types: !10)
+!10 = !{!11}
+!11 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!12 = !DILocation(line: 1, column: 24, scope: !7)
+!13 = distinct !DISubprogram(name: "bar", linkageName: "_ZN3xyz3barEv", scope: !14, file: !8, line: 3, type: !9, scopeLine: 3, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !2)
+!14 = !DINamespace(name: "xyz", scope: null)
+!15 = !DILocation(line: 3, column: 13, scope: !13)
+!16 = distinct !DISubprogram(name: "baz", linkageName: "_ZN3xyz3bazEv", scope: !14, file: !8, line: 3, type: !9, scopeLine: 3, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !2)
+!17 = !DILocation(line: 3, column: 38, scope: !16)
diff --git a/llvm/test/tools/llvm-objdump/X86/source-interleave-x86_64.test b/llvm/test/tools/llvm-objdump/X86/source-interleave-x86_64.test
index 63a6c05..198d4ed 100644
--- a/llvm/test/tools/llvm-objdump/X86/source-interleave-x86_64.test
+++ b/llvm/test/tools/llvm-objdump/X86/source-interleave-x86_64.test
@@ -10,6 +10,7 @@
 # RUN: FileCheck --check-prefix=SOURCE --strict-whitespace %s < %t2
 
 # LINES: main:
+# LINES-NEXT: ; main():
 # LINES-NEXT: ; {{[ -\(\)_A-Za-z0-9.\\/:]+}}source-interleave-x86_64.c:6
 
 # SOURCE: main:
diff --git a/llvm/test/tools/llvm-objdump/embedded-source.test b/llvm/test/tools/llvm-objdump/embedded-source.test
index dd30471..0d8786b 100644
--- a/llvm/test/tools/llvm-objdump/embedded-source.test
+++ b/llvm/test/tools/llvm-objdump/embedded-source.test
@@ -13,6 +13,7 @@
 ; }
 
 ; LINE: main:
+; LINE-NEXT: ; main():
 ; LINE-NEXT: ; {{.*}}embedded-source.c:1
 ; LINE-NEXT: pushq %rbp
 ; LINE: ; {{.*}}embedded-source.c:2
diff --git a/llvm/tools/llvm-objdump/llvm-objdump.cpp b/llvm/tools/llvm-objdump/llvm-objdump.cpp
index 1b98d78..c07a4ec 100644
--- a/llvm/tools/llvm-objdump/llvm-objdump.cpp
+++ b/llvm/tools/llvm-objdump/llvm-objdump.cpp
@@ -553,13 +553,20 @@
 private:
   bool cacheSource(const DILineInfo& LineInfoFile);
 
+  void printLines(raw_ostream &OS, const DILineInfo &LineInfo,
+                  StringRef Delimiter);
+
+  void printSources(raw_ostream &OS, const DILineInfo &LineInfo,
+                    StringRef ObjectFilename, StringRef Delimiter);
+
 public:
   SourcePrinter() = default;
   SourcePrinter(const ObjectFile *Obj, StringRef DefaultArch)
       : Obj(Obj), WarnedNoDebugInfo(false) {
     symbolize::LLVMSymbolizer::Options SymbolizerOpts;
-    SymbolizerOpts.PrintFunctions = DILineInfoSpecifier::FunctionNameKind::None;
-    SymbolizerOpts.Demangle = false;
+    SymbolizerOpts.PrintFunctions =
+        DILineInfoSpecifier::FunctionNameKind::LinkageName;
+    SymbolizerOpts.Demangle = Demangle;
     SymbolizerOpts.DefaultArch = std::string(DefaultArch);
     Symbolizer.reset(new symbolize::LLVMSymbolizer(SymbolizerOpts));
   }
@@ -624,36 +631,59 @@
       reportWarning(Warning, ObjectFilename);
       WarnedNoDebugInfo = true;
     }
-    return;
   }
 
-  if (LineInfo.Line == 0 || ((OldLineInfo.Line == LineInfo.Line) &&
-                             (OldLineInfo.FileName == LineInfo.FileName)))
-    return;
-
   if (PrintLines)
-    OS << Delimiter << LineInfo.FileName << ":" << LineInfo.Line << "\n";
-  if (PrintSource) {
-    if (SourceCache.find(LineInfo.FileName) == SourceCache.end())
-      if (!cacheSource(LineInfo))
-        return;
-    auto LineBuffer = LineCache.find(LineInfo.FileName);
-    if (LineBuffer != LineCache.end()) {
-      if (LineInfo.Line > LineBuffer->second.size()) {
-        reportWarning(
-            formatv(
-                "debug info line number {0} exceeds the number of lines in {1}",
-                LineInfo.Line, LineInfo.FileName),
-            ObjectFilename);
-        return;
-      }
-      // Vector begins at 0, line numbers are non-zero
-      OS << Delimiter << LineBuffer->second[LineInfo.Line - 1] << '\n';
-    }
-  }
+    printLines(OS, LineInfo, Delimiter);
+  if (PrintSource)
+    printSources(OS, LineInfo, ObjectFilename, Delimiter);
   OldLineInfo = LineInfo;
 }
 
+void SourcePrinter::printLines(raw_ostream &OS, const DILineInfo &LineInfo,
+                               StringRef Delimiter) {
+  bool PrintFunctionName = LineInfo.FunctionName != DILineInfo::BadString &&
+                           LineInfo.FunctionName != OldLineInfo.FunctionName;
+  if (PrintFunctionName) {
+    OS << Delimiter << LineInfo.FunctionName;
+    // If demangling is successful, FunctionName will end with "()". Print it
+    // only if demangling did not run or was unsuccessful.
+    if (!StringRef(LineInfo.FunctionName).endswith("()"))
+      OS << "()";
+    OS << ":\n";
+  }
+  if (LineInfo.FileName != DILineInfo::BadString && LineInfo.Line != 0 &&
+      (OldLineInfo.Line != LineInfo.Line ||
+       OldLineInfo.FileName != LineInfo.FileName || PrintFunctionName))
+    OS << Delimiter << LineInfo.FileName << ":" << LineInfo.Line << "\n";
+}
+
+void SourcePrinter::printSources(raw_ostream &OS, const DILineInfo &LineInfo,
+                                 StringRef ObjectFilename,
+                                 StringRef Delimiter) {
+  if (LineInfo.FileName == DILineInfo::BadString || LineInfo.Line == 0 ||
+      (OldLineInfo.Line == LineInfo.Line &&
+       OldLineInfo.FileName == LineInfo.FileName))
+    return;
+
+  if (SourceCache.find(LineInfo.FileName) == SourceCache.end())
+    if (!cacheSource(LineInfo))
+      return;
+  auto LineBuffer = LineCache.find(LineInfo.FileName);
+  if (LineBuffer != LineCache.end()) {
+    if (LineInfo.Line > LineBuffer->second.size()) {
+      reportWarning(
+          formatv(
+              "debug info line number {0} exceeds the number of lines in {1}",
+              LineInfo.Line, LineInfo.FileName),
+          ObjectFilename);
+      return;
+    }
+    // Vector begins at 0, line numbers are non-zero
+    OS << Delimiter << LineBuffer->second[LineInfo.Line - 1] << '\n';
+  }
+}
+
 static bool isAArch64Elf(const ObjectFile *Obj) {
   const auto *Elf = dyn_cast<ELFObjectFileBase>(Obj);
   return Elf && Elf->getEMachine() == ELF::EM_AARCH64;