[llvm] [llvm-objdump] Add the --visualize-jumps option (PR #74858)

via llvm-commits llvm-commits at lists.llvm.org
Fri Dec 8 08:23:01 PST 2023


https://github.com/ostannard created https://github.com/llvm/llvm-project/pull/74858

This is a feature which GNU objdump currently has, which prints a control-flow graph showing all of the direct branches alongside the disassembly.

This should work for any architecture which implements the MCInstrAnalysis class, I've tested it on ARM, Thumb and AArch64.

There are a few (known) differences between this and the GNU objdump
version:
* This has the option to draw the lines using unicode line-drawing charcters, instead of ASCII. This is on by default because I find the connected lines much easier to read. I've included an option to revert back to ASCII for terminals or fonts which don't support the right bits of unicode.
* I haven't yet implemented the extended-color mode yet. With GNU objdump this often results in some very similar colors, so we might want to carefully pick a pallet which minimises that.

Related issue: #60172

Demo:  ![Screenshot from 2023-12-08 16-11-24](https://github.com/llvm/llvm-project/assets/3903045/5267f249-5e6a-438d-a026-135f015ee0aa)

>From 0939afeaaf725f4234a69c46e9d83eb301812ec2 Mon Sep 17 00:00:00 2001
From: Oliver Stannard <oliver.stannard at arm.com>
Date: Fri, 1 Dec 2023 17:07:00 +0000
Subject: [PATCH 1/5] [llvm-objdump] Use formatted_raw_ostream for more things

---
 llvm/tools/llvm-objdump/llvm-objdump.cpp | 60 +++++++++++++-----------
 1 file changed, 33 insertions(+), 27 deletions(-)

diff --git a/llvm/tools/llvm-objdump/llvm-objdump.cpp b/llvm/tools/llvm-objdump/llvm-objdump.cpp
index 631ca955776d42..4297dc8875d348 100644
--- a/llvm/tools/llvm-objdump/llvm-objdump.cpp
+++ b/llvm/tools/llvm-objdump/llvm-objdump.cpp
@@ -1193,7 +1193,7 @@ static uint64_t dumpARMELFData(uint64_t SectionAddr, uint64_t Index,
 }
 
 static void dumpELFData(uint64_t SectionAddr, uint64_t Index, uint64_t End,
-                        ArrayRef<uint8_t> Bytes) {
+                        ArrayRef<uint8_t> Bytes, formatted_raw_ostream &OS) {
   // print out data up to 8 bytes at a time in hex and ascii
   uint8_t AsciiData[9] = {'\0'};
   uint8_t Byte;
@@ -1201,9 +1201,9 @@ static void dumpELFData(uint64_t SectionAddr, uint64_t Index, uint64_t End,
 
   for (; Index < End; ++Index) {
     if (NumBytes == 0)
-      outs() << format("%8" PRIx64 ":", SectionAddr + Index);
+      OS << format("%8" PRIx64 ":", SectionAddr + Index);
     Byte = Bytes.slice(Index)[0];
-    outs() << format(" %02x", Byte);
+    OS << format(" %02x", Byte);
     AsciiData[NumBytes] = isPrint(Byte) ? Byte : '.';
 
     uint8_t IndentOffset = 0;
@@ -1218,9 +1218,9 @@ static void dumpELFData(uint64_t SectionAddr, uint64_t Index, uint64_t End,
     }
     if (NumBytes == 8) {
       AsciiData[8] = '\0';
-      outs() << std::string(IndentOffset, ' ') << "         ";
-      outs() << reinterpret_cast<char *>(AsciiData);
-      outs() << '\n';
+      OS << std::string(IndentOffset, ' ') << "         ";
+      OS << reinterpret_cast<char *>(AsciiData);
+      OS << '\n';
       NumBytes = 0;
     }
   }
@@ -1850,12 +1850,15 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj,
       Start -= SectionAddr;
       End -= SectionAddr;
 
+
+      formatted_raw_ostream FOS(outs());
+
       if (!PrintedSection) {
         PrintedSection = true;
-        outs() << "\nDisassembly of section ";
+        FOS << "\nDisassembly of section ";
         if (!SegmentName.empty())
-          outs() << SegmentName << ",";
-        outs() << SectionName << ":\n";
+          FOS << SegmentName << ",";
+        FOS << SectionName << ":\n";
       }
 
       bool PrintedLabel = false;
@@ -1867,22 +1870,23 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj,
         const StringRef SymbolName = SymNamesHere[i];
 
         if (!PrintedLabel) {
-          outs() << '\n';
+          FOS << '\n';
           PrintedLabel = true;
         }
         if (LeadingAddr)
-          outs() << format(Is64Bits ? "%016" PRIx64 " " : "%08" PRIx64 " ",
-                           SectionAddr + Start + VMAAdjustment);
+          FOS << format(Is64Bits ? "%016" PRIx64 " " : "%08" PRIx64 " ",
+                        SectionAddr + Start + VMAAdjustment);
         if (Obj.isXCOFF() && SymbolDescription) {
-          outs() << getXCOFFSymbolDescription(Symbol, SymbolName) << ":\n";
+          FOS << getXCOFFSymbolDescription(Symbol, SymbolName) << ":";
         } else
-          outs() << '<' << SymbolName << ">:\n";
+          FOS << '<' << SymbolName << ">:";
+        LVP.printAfterOtherLine(FOS, false);
       }
 
       // Don't print raw contents of a virtual section. A virtual section
       // doesn't have any contents in the file.
       if (Section.isVirtual()) {
-        outs() << "...\n";
+        FOS << "...\n";
         continue;
       }
 
@@ -1926,12 +1930,12 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj,
           // distance to the next symbol, and sometimes it will be just a
           // prologue and we should start disassembling instructions from where
           // it left off.
-          outs() << DT->Context->getAsmInfo()->getCommentString()
-                 << " error in decoding " << SymNamesHere[SHI]
-                 << " : decoding failed region as bytes.\n";
+          FOS << DT->Context->getAsmInfo()->getCommentString()
+              << " error in decoding " << SymNamesHere[SHI]
+              << " : decoding failed region as bytes.\n";
           for (uint64_t I = 0; I < Size; ++I) {
-            outs() << "\t.byte\t " << format_hex(Bytes[I], 1, /*Upper=*/true)
-                   << "\n";
+            FOS << "\t.byte\t " << format_hex(Bytes[I], 1, /*Upper=*/true)
+                << "\n";
           }
         }
         Start += Size;
@@ -1943,7 +1947,7 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj,
         Index = std::max<uint64_t>(Index, StartAddress - SectionAddr);
 
       if (DisassembleAsELFData) {
-        dumpELFData(SectionAddr, Index, End, Bytes);
+        dumpELFData(SectionAddr, Index, End, Bytes, FOS);
         Index = End;
         continue;
       }
@@ -1954,8 +1958,6 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj,
           Symbols[SI - 1].XCOFFSymInfo.StorageMappingClass &&
           (*Symbols[SI - 1].XCOFFSymInfo.StorageMappingClass == XCOFF::XMC_PR);
 
-      formatted_raw_ostream FOS(outs());
-
       // FIXME: Workaround for bug in formatted_raw_ostream. Color escape codes
       // are (incorrectly) written directly to the unbuffered raw_ostream
       // wrapped by the formatted_raw_ostream.
@@ -2047,12 +2049,16 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj,
           // Print local label if there's any.
           auto Iter1 = BBAddrMapLabels.find(SectionAddr + Index);
           if (Iter1 != BBAddrMapLabels.end()) {
-            for (StringRef Label : Iter1->second)
-              FOS << "<" << Label << ">:\n";
+            for (StringRef Label : Iter1->second) {
+              FOS << "<" << Label << ">:";
+              LVP.printAfterOtherLine(FOS, true);
+            }
           } else {
             auto Iter2 = AllLabels.find(SectionAddr + Index);
-            if (Iter2 != AllLabels.end())
-              FOS << "<" << Iter2->second << ">:\n";
+            if (Iter2 != AllLabels.end()) {
+              FOS << "<" << Iter2->second << ">:";
+              LVP.printAfterOtherLine(FOS, true);
+            }
           }
 
           // Disassemble a real instruction or a data when disassemble all is

>From 4e84f3923d9804b635c65fb33220a7e9e85dc17f Mon Sep 17 00:00:00 2001
From: Oliver Stannard <oliver.stannard at arm.com>
Date: Fri, 1 Dec 2023 16:52:29 +0000
Subject: [PATCH 2/5] [llvm-objdump] Refactor column width handling (NFCI)

This moves all of the logic for deciding the widths of different parts
of the disassembly and indenting to them into one place. This will make
it easier to add new columns. It also simplifies some things, because we
were already using formatted_raw_ostream almost everywhere, which tracks
the current column number for us.
---
 llvm/tools/llvm-objdump/SourcePrinter.cpp |  2 +-
 llvm/tools/llvm-objdump/XCOFFDump.cpp     |  2 +-
 llvm/tools/llvm-objdump/llvm-objdump.cpp  | 92 ++++++++++++++++++-----
 llvm/tools/llvm-objdump/llvm-objdump.h    | 11 +++
 4 files changed, 87 insertions(+), 20 deletions(-)

diff --git a/llvm/tools/llvm-objdump/SourcePrinter.cpp b/llvm/tools/llvm-objdump/SourcePrinter.cpp
index b2fe56cf2e1cc0..31101bccf4ae60 100644
--- a/llvm/tools/llvm-objdump/SourcePrinter.cpp
+++ b/llvm/tools/llvm-objdump/SourcePrinter.cpp
@@ -95,7 +95,7 @@ void LiveVariablePrinter::addFunction(DWARFDie D) {
 // Get the column number (in characters) at which the first live variable
 // line should be printed.
 unsigned LiveVariablePrinter::getIndentLevel() const {
-  return DbgIndent + getInstStartColumn(STI);
+  return GetColumnIndent(STI, DisassemblyColumn::Variables);
 }
 
 // Indent to the first live-range column to the right of the currently
diff --git a/llvm/tools/llvm-objdump/XCOFFDump.cpp b/llvm/tools/llvm-objdump/XCOFFDump.cpp
index 0f6147924f8a1a..e6266dcd774d68 100644
--- a/llvm/tools/llvm-objdump/XCOFFDump.cpp
+++ b/llvm/tools/llvm-objdump/XCOFFDump.cpp
@@ -150,7 +150,7 @@ void objdump::dumpTracebackTable(ArrayRef<uint8_t> Bytes, uint64_t Address,
                                  const MCSubtargetInfo &STI,
                                  const XCOFFObjectFile *Obj) {
   uint64_t Index = 0;
-  unsigned TabStop = getInstStartColumn(STI) - 1;
+  unsigned TabStop = GetColumnIndent(STI, DisassemblyColumn::Assembly) - 1;
   // Print traceback table boundary.
   printRawData(Bytes.slice(Index, 4), Address, OS, STI);
   OS << "\t# Traceback table start\n";
diff --git a/llvm/tools/llvm-objdump/llvm-objdump.cpp b/llvm/tools/llvm-objdump/llvm-objdump.cpp
index 4297dc8875d348..80f2f8de06b0d9 100644
--- a/llvm/tools/llvm-objdump/llvm-objdump.cpp
+++ b/llvm/tools/llvm-objdump/llvm-objdump.cpp
@@ -477,26 +477,82 @@ unsigned objdump::getInstStartColumn(const MCSubtargetInfo &STI) {
   return !ShowRawInsn ? 16 : STI.getTargetTriple().isX86() ? 40 : 24;
 }
 
-static void AlignToInstStartColumn(size_t Start, const MCSubtargetInfo &STI,
-                                   raw_ostream &OS) {
-  // The output of printInst starts with a tab. Print some spaces so that
-  // the tab has 1 column and advances to the target tab stop.
-  unsigned TabStop = getInstStartColumn(STI);
-  unsigned Column = OS.tell() - Start;
-  OS.indent(Column < TabStop - 1 ? TabStop - 1 - Column : 7 - Column % 8);
+unsigned EncodingColumnWidth(Triple const &Triple) {
+  switch (Triple.getArch()) {
+  case Triple::x86:
+  case Triple::x86_64:
+    return 30;
+  default:
+    return 14;
+  }
+}
+
+unsigned objdump::GetColumnIndent(MCSubtargetInfo const &STI,
+                                  DisassemblyColumn Col) {
+  unsigned Indent = 0;
+
+  if (Col == DisassemblyColumn::Address)
+    return Indent;
+
+  // Address: 8 chars of address, followed by ": "
+  Indent += 10;
+
+  if (Col == DisassemblyColumn::Encoding)
+    return Indent;
+
+  // Encoding: depends on architecture
+  if (ShowRawInsn)
+      Indent += EncodingColumnWidth(STI.getTargetTriple());
+
+  // Special case for assembly string: the assembly printer uses tabs, so we
+  // need to ensure we start the instruction on a tab stop (multiple of 8).
+  Indent = alignTo(Indent, 8);
+  if (Col == DisassemblyColumn::Assembly) {
+    return Indent;
+  }
+
+  // Assembly width can be configured with --debug-vars-indent=
+  // FIXME this variable name is confusing.
+  Indent += DbgIndent;
+
+  if (Col == DisassemblyColumn::Variables)
+    return Indent;
+
+  llvm_unreachable("Unhandled DisassemblyColumn");
+}
+
+void objdump::IndentToColumn(MCSubtargetInfo const &STI,
+                             formatted_raw_ostream &OS, DisassemblyColumn Col) {
+  unsigned TargetIndent = GetColumnIndent(STI, Col);
+  unsigned CurrentIndent = OS.getColumn();
+
+  // Special case for assembly string: the output of printInst starts with a
+  // tab, so we want to start printing one character before a tab stop, so it
+  // always has a width of one. GetColumnIndent already guarantees that
+  // TargetIndent will be a multple of 8.
+  // TODO Add a way to avoid printing the leading tab, to simplify this.
+  // TODO 7 characters isn't enough for a lot of mnemonics, add an option to
+  // increase the gap to the first operand.
+  if (Col == DisassemblyColumn::Assembly) {
+    TargetIndent -= 1;
+    if (TargetIndent < CurrentIndent)
+      TargetIndent =  alignTo(CurrentIndent + 1, 8) - 1;
+  }
+
+  if (TargetIndent > CurrentIndent)
+    OS.indent(TargetIndent - CurrentIndent);
 }
 
 void objdump::printRawData(ArrayRef<uint8_t> Bytes, uint64_t Address,
                            formatted_raw_ostream &OS,
                            MCSubtargetInfo const &STI) {
-  size_t Start = OS.tell();
   if (LeadingAddr)
     OS << format("%8" PRIx64 ":", Address);
   if (ShowRawInsn) {
     OS << ' ';
     dumpBytes(Bytes, OS);
   }
-  AlignToInstStartColumn(Start, STI, OS);
+  IndentToColumn(STI, OS, DisassemblyColumn::Assembly);
 }
 
 namespace {
@@ -566,6 +622,8 @@ class PrettyPrinter {
 
     printRawData(Bytes, Address.Address, OS, STI);
 
+    IndentToColumn(STI, OS, DisassemblyColumn::Assembly);
+
     if (MI) {
       // See MCInstPrinter::printInst. On targets where a PC relative immediate
       // is relative to the next instruction and the length of a MCInst is
@@ -749,7 +807,6 @@ class ARMPrettyPrinter : public PrettyPrinter {
       SP->printSourceLine(OS, Address, ObjectFilename, LVP);
     LVP.printBetweenInsts(OS, false);
 
-    size_t Start = OS.tell();
     if (LeadingAddr)
       OS << format("%8" PRIx64 ":", Address.Address);
     if (ShowRawInsn) {
@@ -775,7 +832,7 @@ class ARMPrettyPrinter : public PrettyPrinter {
       }
     }
 
-    AlignToInstStartColumn(Start, STI, OS);
+    IndentToColumn(STI, OS, DisassemblyColumn::Assembly);
 
     if (MI) {
       IP.printInst(MI, Address.Address, "", STI, OS);
@@ -803,7 +860,6 @@ class AArch64PrettyPrinter : public PrettyPrinter {
       SP->printSourceLine(OS, Address, ObjectFilename, LVP);
     LVP.printBetweenInsts(OS, false);
 
-    size_t Start = OS.tell();
     if (LeadingAddr)
       OS << format("%8" PRIx64 ":", Address.Address);
     if (ShowRawInsn) {
@@ -820,7 +876,7 @@ class AArch64PrettyPrinter : public PrettyPrinter {
       }
     }
 
-    AlignToInstStartColumn(Start, STI, OS);
+    IndentToColumn(STI, OS, DisassemblyColumn::Assembly);
 
     if (MI) {
       IP.printInst(MI, Address.Address, "", STI, OS);
@@ -1166,14 +1222,14 @@ static uint64_t dumpARMELFData(uint64_t SectionAddr, uint64_t Index,
                                uint64_t End, const ObjectFile &Obj,
                                ArrayRef<uint8_t> Bytes,
                                ArrayRef<MappingSymbolPair> MappingSymbols,
-                               const MCSubtargetInfo &STI, raw_ostream &OS) {
+                               const MCSubtargetInfo &STI,
+                               formatted_raw_ostream &OS) {
   llvm::endianness Endian =
       Obj.isLittleEndian() ? llvm::endianness::little : llvm::endianness::big;
-  size_t Start = OS.tell();
   OS << format("%8" PRIx64 ": ", SectionAddr + Index);
   if (Index + 4 <= End) {
     dumpBytes(Bytes.slice(Index, 4), OS);
-    AlignToInstStartColumn(Start, STI, OS);
+    IndentToColumn(STI, OS, DisassemblyColumn::Assembly);
     OS << "\t.word\t"
            << format_hex(support::endian::read32(Bytes.data() + Index, Endian),
                          10);
@@ -1181,13 +1237,13 @@ static uint64_t dumpARMELFData(uint64_t SectionAddr, uint64_t Index,
   }
   if (Index + 2 <= End) {
     dumpBytes(Bytes.slice(Index, 2), OS);
-    AlignToInstStartColumn(Start, STI, OS);
+    IndentToColumn(STI, OS, DisassemblyColumn::Assembly);
     OS << "\t.short\t"
        << format_hex(support::endian::read16(Bytes.data() + Index, Endian), 6);
     return 2;
   }
   dumpBytes(Bytes.slice(Index, 1), OS);
-  AlignToInstStartColumn(Start, STI, OS);
+  IndentToColumn(STI, OS, DisassemblyColumn::Assembly);
   OS << "\t.byte\t" << format_hex(Bytes[Index], 4);
   return 1;
 }
diff --git a/llvm/tools/llvm-objdump/llvm-objdump.h b/llvm/tools/llvm-objdump/llvm-objdump.h
index 7778cf6c2784eb..31281ba3d8c2ad 100644
--- a/llvm/tools/llvm-objdump/llvm-objdump.h
+++ b/llvm/tools/llvm-objdump/llvm-objdump.h
@@ -144,6 +144,17 @@ void printRawData(llvm::ArrayRef<uint8_t> Bytes, uint64_t Address,
                   llvm::formatted_raw_ostream &OS,
                   llvm::MCSubtargetInfo const &STI);
 
+enum class DisassemblyColumn {
+  Address,
+  Encoding,
+  Assembly,
+  Variables,
+};
+
+unsigned GetColumnIndent(MCSubtargetInfo const &STI, DisassemblyColumn Col);
+void IndentToColumn(MCSubtargetInfo const &STI, formatted_raw_ostream &OS,
+                    DisassemblyColumn Col);
+
 } // namespace objdump
 } // end namespace llvm
 

>From 28f3b9525ae880ed00f2143d34ac67f4f709a49c Mon Sep 17 00:00:00 2001
From: Oliver Stannard <oliver.stannard at arm.com>
Date: Fri, 1 Dec 2023 17:08:02 +0000
Subject: [PATCH 3/5] [llvm-objdump] Collect branch targets for all
 architectures

---
 llvm/tools/llvm-objdump/llvm-objdump.cpp | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/llvm/tools/llvm-objdump/llvm-objdump.cpp b/llvm/tools/llvm-objdump/llvm-objdump.cpp
index 80f2f8de06b0d9..80604bff825664 100644
--- a/llvm/tools/llvm-objdump/llvm-objdump.cpp
+++ b/llvm/tools/llvm-objdump/llvm-objdump.cpp
@@ -1344,10 +1344,6 @@ collectLocalBranchTargets(ArrayRef<uint8_t> Bytes, MCInstrAnalysis *MIA,
                           const MCSubtargetInfo *STI, uint64_t SectionAddr,
                           uint64_t Start, uint64_t End,
                           std::unordered_map<uint64_t, std::string> &Labels) {
-  // So far only supports PowerPC and X86.
-  if (!STI->getTargetTriple().isPPC() && !STI->getTargetTriple().isX86())
-    return;
-
   if (MIA)
     MIA->resetState();
 

>From 1e1ebb30f973980825dd2fdec7bd6b850d5167c9 Mon Sep 17 00:00:00 2001
From: Oliver Stannard <oliver.stannard at arm.com>
Date: Fri, 1 Dec 2023 17:14:28 +0000
Subject: [PATCH 4/5] [Support] Tab always advances column by at least 1

---
 llvm/lib/Support/FormattedStream.cpp                  | 1 +
 llvm/unittests/Support/formatted_raw_ostream_test.cpp | 9 ++++-----
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/llvm/lib/Support/FormattedStream.cpp b/llvm/lib/Support/FormattedStream.cpp
index c0d28435099570..48b02889c6d3e5 100644
--- a/llvm/lib/Support/FormattedStream.cpp
+++ b/llvm/lib/Support/FormattedStream.cpp
@@ -45,6 +45,7 @@ void formatted_raw_ostream::UpdatePosition(const char *Ptr, size_t Size) {
       break;
     case '\t':
       // Assumes tab stop = 8 characters.
+      Column += 1;
       Column += (8 - (Column & 0x7)) & 0x7;
       break;
     }
diff --git a/llvm/unittests/Support/formatted_raw_ostream_test.cpp b/llvm/unittests/Support/formatted_raw_ostream_test.cpp
index 750dc518fee388..5ae5608d0bdb07 100644
--- a/llvm/unittests/Support/formatted_raw_ostream_test.cpp
+++ b/llvm/unittests/Support/formatted_raw_ostream_test.cpp
@@ -56,15 +56,14 @@ TEST(formatted_raw_ostreamTest, Test_LineColumn) {
   EXPECT_EQ(1U, C.getLine());
   EXPECT_EQ(0U, C.getColumn());
 
-  // '\t' advances column to the next multiple of 8.
-  // FIXME: If the column number is already a multiple of 8 this will do
-  // nothing, is this behaviour correct?
+  // '\t' advances column to the next multiple of 8, and always by at least 1
+  // column.
   C << "1\t";
   EXPECT_EQ(8U, C.getColumn());
   C << "\t";
-  EXPECT_EQ(8U, C.getColumn());
-  C << "1234567\t";
   EXPECT_EQ(16U, C.getColumn());
+  C << "1234567\t";
+  EXPECT_EQ(24U, C.getColumn());
   EXPECT_EQ(1U, C.getLine());
 }
 

>From 8edb1fdbb74d754080c00bf2ba5ec32b21782cc8 Mon Sep 17 00:00:00 2001
From: Oliver Stannard <oliver.stannard at arm.com>
Date: Fri, 1 Dec 2023 17:18:19 +0000
Subject: [PATCH 5/5] [llvm-objdump] Add the --visualize-jumps option

This is a feature which GNU objdump currently has, which prints a
control-flow graph showing all of the direct branches alongside the
disassembly.

This should work for any architecture which implements the
MCInstrAnalysis class, I've tested it on ARM, Thumb and AArch64.

There are a few (known) differences between this and the GNU objdump
version:
* This has the option to draw the lines using unicode line-drawing
  charcters, instead of ASCII. This is on by default because I find the
  connected lines much easier to read.
* I haven't yet implemented the extended-color mode yet. With GNU
  objdump this often results in some very similar colors, so we might
  want to carefully pick a pallete which minimises that.
---
 .../Inputs/visualize-jumps-aarch64-ascii.txt  |  31 +++
 .../visualize-jumps-aarch64-unicode-color.txt |  31 +++
 ...visualize-jumps-aarch64-unicode-relocs.txt |  34 +++
 .../visualize-jumps-aarch64-unicode.txt       |  31 +++
 .../Inputs/visualize-jumps-arm-ascii.txt      |  27 +++
 .../Inputs/visualize-jumps-arm-unicode.txt    |  27 +++
 .../Inputs/visualize-jumps-thumb-ascii.txt    |  34 +++
 .../Inputs/visualize-jumps-thumb-unicode.txt  |  34 +++
 .../llvm-objdump/visualize-jumps-aarch64.s    |  69 +++++++
 .../tools/llvm-objdump/visualize-jumps-arm.s  |  54 +++++
 .../llvm-objdump/visualize-jumps-thumb.s      |  63 ++++++
 llvm/tools/llvm-objdump/ObjdumpOpts.td        |   7 +
 llvm/tools/llvm-objdump/SourcePrinter.cpp     | 194 +++++++++++++++++-
 llvm/tools/llvm-objdump/SourcePrinter.h       | 113 +++++++++-
 llvm/tools/llvm-objdump/llvm-objdump.cpp      | 159 +++++++++++---
 llvm/tools/llvm-objdump/llvm-objdump.h        |   2 +
 16 files changed, 877 insertions(+), 33 deletions(-)
 create mode 100644 llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-aarch64-ascii.txt
 create mode 100644 llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-aarch64-unicode-color.txt
 create mode 100644 llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-aarch64-unicode-relocs.txt
 create mode 100644 llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-aarch64-unicode.txt
 create mode 100644 llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-arm-ascii.txt
 create mode 100644 llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-arm-unicode.txt
 create mode 100644 llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-thumb-ascii.txt
 create mode 100644 llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-thumb-unicode.txt
 create mode 100644 llvm/test/tools/llvm-objdump/visualize-jumps-aarch64.s
 create mode 100644 llvm/test/tools/llvm-objdump/visualize-jumps-arm.s
 create mode 100644 llvm/test/tools/llvm-objdump/visualize-jumps-thumb.s

diff --git a/llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-aarch64-ascii.txt b/llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-aarch64-ascii.txt
new file mode 100644
index 00000000000000..592e50d98f8ff7
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-aarch64-ascii.txt
@@ -0,0 +1,31 @@
+
+<stdin>:	file format elf64-littleaarch64
+
+Disassembly of section .text:
+
+0000000000000000 <test_func>:
+       0:           94000000   	bl	0x0 <test_func>
+       4:           14000000   	b	0x4 <test_func+0x4>
+       8:       /-- 14000001   	b	0xc <test_func+0xc>
+       c:       +-> d503201f   	nop
+      10:       \-- 17ffffff   	b	0xc <test_func+0xc>
+      14:           14000000   	b	0x14 <test_func+0x14>
+      18:       /-- 54000040   	b.eq	0x20 <test_func+0x20>
+      1c:       +-- b4000020   	cbz	x0, 0x20 <test_func+0x20>
+      20:       \-> d503201f   	nop
+      24:   /------ 14000005   	b	0x38 <test_func+0x38>
+      28:   | /---- 14000003   	b	0x34 <test_func+0x34>
+      2c:   | | /-- 14000001   	b	0x30 <test_func+0x30>
+      30:   | | \-> d503201f   	nop
+      34:   | \---> d503201f   	nop
+      38:   \-----> d503201f   	nop
+      3c:       /-- 14000002   	b	0x44 <test_func+0x44>
+      40:     /-|-- 14000002   	b	0x48 <test_func+0x48>
+      44:     | \-> d503201f   	nop
+      48:     \---> d503201f   	nop
+      4c:       /-- 14000001   	b	0x50 <test_func+0x50>
+      50:     /-|-- 14000001   	b	0x54 <test_func+0x54>
+      54:     \---> d503201f   	nop
+      58:       /-- 14000002   	b	0x60 <test_func+0x60>
+      5c:       |   94000000   	bl	0x5c <test_func+0x5c>
+      60:       \-> d503201f   	nop
diff --git a/llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-aarch64-unicode-color.txt b/llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-aarch64-unicode-color.txt
new file mode 100644
index 00000000000000..925c5ef52a8d22
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-aarch64-unicode-color.txt
@@ -0,0 +1,31 @@
+
+<stdin>:	file format elf64-littleaarch64
+
+Disassembly of section .text:
+
+0000000000000000 <test_func>:
+       0:           94000000   	bl	0x0 <test_func>
+       4:           14000000   	b	0x4 <test_func+0x4>
+       8:       ╭── 14000001   	b	0xc <test_func+0xc>
+       c:       ├─> d503201f   	nop
+      10:       ╰── 17ffffff   	b	0xc <test_func+0xc>
+      14:           14000000   	b	0x14 <test_func+0x14>
+      18:       ╭── 54000040   	b.eq	0x20 <test_func+0x20>
+      1c:       ├── b4000020   	cbz	x0, 0x20 <test_func+0x20>
+      20:       ╰─> d503201f   	nop
+      24:   ╭────── 14000005   	b	0x38 <test_func+0x38>
+      28:   │ ╭──── 14000003   	b	0x34 <test_func+0x34>
+      2c:   │ │ ╭── 14000001   	b	0x30 <test_func+0x30>
+      30:   │ │ ╰─> d503201f   	nop
+      34:   │ ╰───> d503201f   	nop
+      38:   ╰─────> d503201f   	nop
+      3c:       ╭── 14000002   	b	0x44 <test_func+0x44>
+      40:     ╭─│── 14000002   	b	0x48 <test_func+0x48>
+      44:     │ ╰─> d503201f   	nop
+      48:     ╰───> d503201f   	nop
+      4c:       ╭── 14000001   	b	0x50 <test_func+0x50>
+      50:     ╭─│── 14000001   	b	0x54 <test_func+0x54>
+      54:     ╰───> d503201f   	nop
+      58:       ╭── 14000002   	b	0x60 <test_func+0x60>
+      5c:       │   94000000   	bl	0x5c <test_func+0x5c>
+      60:       ╰─> d503201f   	nop
diff --git a/llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-aarch64-unicode-relocs.txt b/llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-aarch64-unicode-relocs.txt
new file mode 100644
index 00000000000000..75ec5741fdaae2
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-aarch64-unicode-relocs.txt
@@ -0,0 +1,34 @@
+
+<stdin>:	file format elf64-littleaarch64
+
+Disassembly of section .text:
+
+0000000000000000 <test_func>:
+       0:           94000000   	bl	0x0 <test_func>
+                   		0000000000000000:  R_AARCH64_CALL26	extern_func
+       4:           14000000   	b	0x4 <test_func+0x4>
+                   		0000000000000004:  R_AARCH64_JUMP26	extern_func
+       8:       ╭── 14000001   	b	0xc <test_func+0xc>
+       c:       ├─> d503201f   	nop
+      10:       ╰── 17ffffff   	b	0xc <test_func+0xc>
+      14:           14000000   	b	0x14 <test_func+0x14>
+      18:       ╭── 54000040   	b.eq	0x20 <test_func+0x20>
+      1c:       ├── b4000020   	cbz	x0, 0x20 <test_func+0x20>
+      20:       ╰─> d503201f   	nop
+      24:   ╭────── 14000005   	b	0x38 <test_func+0x38>
+      28:   │ ╭──── 14000003   	b	0x34 <test_func+0x34>
+      2c:   │ │ ╭── 14000001   	b	0x30 <test_func+0x30>
+      30:   │ │ ╰─> d503201f   	nop
+      34:   │ ╰───> d503201f   	nop
+      38:   ╰─────> d503201f   	nop
+      3c:       ╭── 14000002   	b	0x44 <test_func+0x44>
+      40:     ╭─│── 14000002   	b	0x48 <test_func+0x48>
+      44:     │ ╰─> d503201f   	nop
+      48:     ╰───> d503201f   	nop
+      4c:       ╭── 14000001   	b	0x50 <test_func+0x50>
+      50:     ╭─│── 14000001   	b	0x54 <test_func+0x54>
+      54:     ╰───> d503201f   	nop
+      58:       ╭── 14000002   	b	0x60 <test_func+0x60>
+      5c:       │   94000000   	bl	0x5c <test_func+0x5c>
+                │  		000000000000005c:  R_AARCH64_CALL26	extern_func
+      60:       ╰─> d503201f   	nop
diff --git a/llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-aarch64-unicode.txt b/llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-aarch64-unicode.txt
new file mode 100644
index 00000000000000..4cabdc92d61c1d
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-aarch64-unicode.txt
@@ -0,0 +1,31 @@
+
+<stdin>:	file format elf64-littleaarch64
+
+Disassembly of section .text:
+
+0000000000000000 <test_func>:
+       0:           94000000   	bl	0x0 <test_func>
+       4:           14000000   	b	0x4 <test_func+0x4>
+       8:       ╭── 14000001   	b	0xc <test_func+0xc>
+       c:       ├─> d503201f   	nop
+      10:       ╰── 17ffffff   	b	0xc <test_func+0xc>
+      14:           14000000   	b	0x14 <test_func+0x14>
+      18:       ╭── 54000040   	b.eq	0x20 <test_func+0x20>
+      1c:       ├── b4000020   	cbz	x0, 0x20 <test_func+0x20>
+      20:       ╰─> d503201f   	nop
+      24:   ╭────── 14000005   	b	0x38 <test_func+0x38>
+      28:   │ ╭──── 14000003   	b	0x34 <test_func+0x34>
+      2c:   │ │ ╭── 14000001   	b	0x30 <test_func+0x30>
+      30:   │ │ ╰─> d503201f   	nop
+      34:   │ ╰───> d503201f   	nop
+      38:   ╰─────> d503201f   	nop
+      3c:       ╭── 14000002   	b	0x44 <test_func+0x44>
+      40:     ╭─│── 14000002   	b	0x48 <test_func+0x48>
+      44:     │ ╰─> d503201f   	nop
+      48:     ╰───> d503201f   	nop
+      4c:       ╭── 14000001   	b	0x50 <test_func+0x50>
+      50:     ╭─│── 14000001   	b	0x54 <test_func+0x54>
+      54:     ╰───> d503201f   	nop
+      58:       ╭── 14000002   	b	0x60 <test_func+0x60>
+      5c:       │   94000000   	bl	0x5c <test_func+0x5c>
+      60:       ╰─> d503201f   	nop
diff --git a/llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-arm-ascii.txt b/llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-arm-ascii.txt
new file mode 100644
index 00000000000000..7e006841980534
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-arm-ascii.txt
@@ -0,0 +1,27 @@
+
+<stdin>:	file format elf32-littlearm
+
+Disassembly of section .text:
+
+00000000 <test_func>:
+       0:           ebfffffe   	bl	0x0 <test_func>         @ imm = #-0x8
+       4:           eafffffe   	b	0x4 <test_func+0x4>     @ imm = #-0x8
+       8:       /-- eaffffff   	b	0xc <test_func+0xc>     @ imm = #-0x4
+       c:       +-> e320f000   	nop
+      10:       \-- eafffffd   	b	0xc <test_func+0xc>     @ imm = #-0xc
+      14:           eafffffe   	b	0x14 <test_func+0x14>   @ imm = #-0x8
+      18:       /-- 0affffff   	beq	0x1c <test_func+0x1c>   @ imm = #-0x4
+      1c:       \-> e320f000   	nop
+      20:   /------ ea000003   	b	0x34 <test_func+0x34>   @ imm = #0xc
+      24:   | /---- ea000001   	b	0x30 <test_func+0x30>   @ imm = #0x4
+      28:   | | /-- eaffffff   	b	0x2c <test_func+0x2c>   @ imm = #-0x4
+      2c:   | | \-> e320f000   	nop
+      30:   | \---> e320f000   	nop
+      34:   \-----> e320f000   	nop
+      38:       /-- ea000000   	b	0x40 <test_func+0x40>   @ imm = #0x0
+      3c:     /-|-- ea000000   	b	0x44 <test_func+0x44>   @ imm = #0x0
+      40:     | \-> e320f000   	nop
+      44:     \---> e320f000   	nop
+      48:     /---- eaffffff   	b	0x4c <test_func+0x4c>   @ imm = #-0x4
+      4c:     \-|-> eaffffff   	b	0x50 <test_func+0x50>   @ imm = #-0x4
+      50:       \-> e320f000   	nop
diff --git a/llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-arm-unicode.txt b/llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-arm-unicode.txt
new file mode 100644
index 00000000000000..62194b4b33c172
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-arm-unicode.txt
@@ -0,0 +1,27 @@
+
+<stdin>:	file format elf32-littlearm
+
+Disassembly of section .text:
+
+00000000 <test_func>:
+       0:           ebfffffe   	bl	0x0 <test_func>         @ imm = #-0x8
+       4:           eafffffe   	b	0x4 <test_func+0x4>     @ imm = #-0x8
+       8:       ╭── eaffffff   	b	0xc <test_func+0xc>     @ imm = #-0x4
+       c:       ├─> e320f000   	nop
+      10:       ╰── eafffffd   	b	0xc <test_func+0xc>     @ imm = #-0xc
+      14:           eafffffe   	b	0x14 <test_func+0x14>   @ imm = #-0x8
+      18:       ╭── 0affffff   	beq	0x1c <test_func+0x1c>   @ imm = #-0x4
+      1c:       ╰─> e320f000   	nop
+      20:   ╭────── ea000003   	b	0x34 <test_func+0x34>   @ imm = #0xc
+      24:   │ ╭──── ea000001   	b	0x30 <test_func+0x30>   @ imm = #0x4
+      28:   │ │ ╭── eaffffff   	b	0x2c <test_func+0x2c>   @ imm = #-0x4
+      2c:   │ │ ╰─> e320f000   	nop
+      30:   │ ╰───> e320f000   	nop
+      34:   ╰─────> e320f000   	nop
+      38:       ╭── ea000000   	b	0x40 <test_func+0x40>   @ imm = #0x0
+      3c:     ╭─│── ea000000   	b	0x44 <test_func+0x44>   @ imm = #0x0
+      40:     │ ╰─> e320f000   	nop
+      44:     ╰───> e320f000   	nop
+      48:     ╭──── eaffffff   	b	0x4c <test_func+0x4c>   @ imm = #-0x4
+      4c:     ╰─│─> eaffffff   	b	0x50 <test_func+0x50>   @ imm = #-0x4
+      50:       ╰─> e320f000   	nop
diff --git a/llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-thumb-ascii.txt b/llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-thumb-ascii.txt
new file mode 100644
index 00000000000000..f18062167dd97d
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-thumb-ascii.txt
@@ -0,0 +1,34 @@
+
+<stdin>:	file format elf32-littlearm
+
+Disassembly of section .text:
+
+00000000 <test_func>:
+       0:           f7ff fffe  	bl	0x0 <test_func>         @ imm = #-0x4
+       4:           f7ff bffe  	b.w	0x4 <test_func+0x4>     @ imm = #-0x4
+       8:       /-- e7ff       	b	0xa <test_func+0xa>     @ imm = #-0x2
+       a:       +-> bf00       	nop
+       c:       \-- e7fd       	b	0xa <test_func+0xa>     @ imm = #-0x6
+       e:           e7fe       	b	0xe <test_func+0xe>     @ imm = #-0x4
+      10:       /-- f000 b807  	b.w	0x22 <test_func+0x22>   @ imm = #0xe
+      14:       +-- d005       	beq	0x22 <test_func+0x22>   @ imm = #0xa
+      16:       +-- f040 8004  	bne.w	0x22 <test_func+0x22>   @ imm = #0x8
+      1a:       +-- b110       	cbz	r0, 0x22 <test_func+0x22> @ imm = #0x4
+      1c:       |   bfd8       	it	le
+      1e:       +-- e000       	ble	0x22 <test_func+0x22>   @ imm = #0x0
+      20:       |   bf00       	nop
+      22:       \-> bf00       	nop
+      24:           bf00       	nop
+      26:   /------ e003       	b	0x30 <test_func+0x30>   @ imm = #0x6
+      28:   | /---- e001       	b	0x2e <test_func+0x2e>   @ imm = #0x2
+      2a:   | | /-- e7ff       	b	0x2c <test_func+0x2c>   @ imm = #-0x2
+      2c:   | | \-> bf00       	nop
+      2e:   | \---> bf00       	nop
+      30:   \-----> bf00       	nop
+      32:       /-- e000       	b	0x36 <test_func+0x36>   @ imm = #0x0
+      34:     /-|-- e000       	b	0x38 <test_func+0x38>   @ imm = #0x0
+      36:     | \-> bf00       	nop
+      38:     \---> bf00       	nop
+      3a:       /-- e7ff       	b	0x3c <test_func+0x3c>   @ imm = #-0x2
+      3c:     /-|-- e7ff       	b	0x3e <test_func+0x3e>   @ imm = #-0x2
+      3e:     \---> bf00       	nop
diff --git a/llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-thumb-unicode.txt b/llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-thumb-unicode.txt
new file mode 100644
index 00000000000000..b171fb1c5d28c2
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/Inputs/visualize-jumps-thumb-unicode.txt
@@ -0,0 +1,34 @@
+
+<stdin>:	file format elf32-littlearm
+
+Disassembly of section .text:
+
+00000000 <test_func>:
+       0:           f7ff fffe  	bl	0x0 <test_func>         @ imm = #-0x4
+       4:           f7ff bffe  	b.w	0x4 <test_func+0x4>     @ imm = #-0x4
+       8:       ╭── e7ff       	b	0xa <test_func+0xa>     @ imm = #-0x2
+       a:       ├─> bf00       	nop
+       c:       ╰── e7fd       	b	0xa <test_func+0xa>     @ imm = #-0x6
+       e:           e7fe       	b	0xe <test_func+0xe>     @ imm = #-0x4
+      10:       ╭── f000 b807  	b.w	0x22 <test_func+0x22>   @ imm = #0xe
+      14:       ├── d005       	beq	0x22 <test_func+0x22>   @ imm = #0xa
+      16:       ├── f040 8004  	bne.w	0x22 <test_func+0x22>   @ imm = #0x8
+      1a:       ├── b110       	cbz	r0, 0x22 <test_func+0x22> @ imm = #0x4
+      1c:       │   bfd8       	it	le
+      1e:       ├── e000       	ble	0x22 <test_func+0x22>   @ imm = #0x0
+      20:       │   bf00       	nop
+      22:       ╰─> bf00       	nop
+      24:           bf00       	nop
+      26:   ╭────── e003       	b	0x30 <test_func+0x30>   @ imm = #0x6
+      28:   │ ╭──── e001       	b	0x2e <test_func+0x2e>   @ imm = #0x2
+      2a:   │ │ ╭── e7ff       	b	0x2c <test_func+0x2c>   @ imm = #-0x2
+      2c:   │ │ ╰─> bf00       	nop
+      2e:   │ ╰───> bf00       	nop
+      30:   ╰─────> bf00       	nop
+      32:       ╭── e000       	b	0x36 <test_func+0x36>   @ imm = #0x0
+      34:     ╭─│── e000       	b	0x38 <test_func+0x38>   @ imm = #0x0
+      36:     │ ╰─> bf00       	nop
+      38:     ╰───> bf00       	nop
+      3a:       ╭── e7ff       	b	0x3c <test_func+0x3c>   @ imm = #-0x2
+      3c:     ╭─│── e7ff       	b	0x3e <test_func+0x3e>   @ imm = #-0x2
+      3e:     ╰───> bf00       	nop
diff --git a/llvm/test/tools/llvm-objdump/visualize-jumps-aarch64.s b/llvm/test/tools/llvm-objdump/visualize-jumps-aarch64.s
new file mode 100644
index 00000000000000..f78b3e27975556
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/visualize-jumps-aarch64.s
@@ -0,0 +1,69 @@
+// RUN: llvm-mc < %s -triple aarch64 -filetype=obj | \
+// RUN:   llvm-objdump --triple aarch64 -d --visualize-jumps=unicode - | \
+// RUN:   diff - %p/Inputs/visualize-jumps-aarch64-unicode.txt
+
+// RUN: llvm-mc < %s -triple aarch64 -filetype=obj | \
+// RUN:   llvm-objdump --triple aarch64 -d --visualize-jumps=ascii - | \
+// RUN:   diff - %p/Inputs/visualize-jumps-aarch64-ascii.txt
+
+// RUN: llvm-mc < %s -triple aarch64 -filetype=obj | \
+// RUN:   llvm-objdump --triple aarch64 -d --visualize-jumps=unicode,color - | \
+// RUN:   diff - %p/Inputs/visualize-jumps-aarch64-unicode-color.txt
+
+// RUN: llvm-mc < %s -triple aarch64 -filetype=obj | \
+// RUN:   llvm-objdump --triple aarch64 -d --visualize-jumps=unicode --reloc - | \
+// RUN:   diff - %p/Inputs/visualize-jumps-aarch64-unicode-relocs.txt
+
+test_func:
+  // Relocated instructions don't get control-flow edges.
+  bl extern_func
+  b extern_func
+  
+  // Two branches to the same label, one forward and one backward.
+  b .Llabel1
+.Llabel1:
+  nop
+  b .Llabel1
+
+  // Branch to self, no CFG edge shown
+  b .
+
+  // Conditional branches
+  b.eq .Llabel2
+  cbz x0, .Llabel2
+.Llabel2:
+  nop
+
+  // Branches are sorted with shorter ones to the right, to reduce number of
+  // crossings, and keep the lines for short branches short themselves.
+  b .Llabel5
+  b .Llabel4
+  b .Llabel3
+.Llabel3:
+  nop
+.Llabel4:
+  nop
+.Llabel5:
+  nop
+
+  // Sometimes crossings can't be avoided.
+  b .Llabel6
+  b .Llabel7
+.Llabel6:
+  nop
+.Llabel7:
+  nop
+
+  // TODO If a branch goes to another branch instruction, we don't have a way
+  // to represent that. Can we improve on this?
+  b .Llabel8
+.Llabel8:
+  b .Llabel9
+.Llabel9:
+  nop
+
+  // Graph lines need to be drawn on the same output line as relocations.
+  b .Llabel10
+  bl extern_func
+.Llabel10:
+  nop
diff --git a/llvm/test/tools/llvm-objdump/visualize-jumps-arm.s b/llvm/test/tools/llvm-objdump/visualize-jumps-arm.s
new file mode 100644
index 00000000000000..6855e6ff84e32a
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/visualize-jumps-arm.s
@@ -0,0 +1,54 @@
+// RUN: llvm-mc < %s -triple armv8a -filetype=obj | \
+// RUN:   llvm-objdump --triple armv8a -d --visualize-jumps=unicode - | \
+// RUN:   diff - %p/Inputs/visualize-jumps-arm-unicode.txt
+
+// RUN: llvm-mc < %s -triple armv8a -filetype=obj | \
+// RUN:   llvm-objdump --triple armv8a -d --visualize-jumps=ascii - | \
+// RUN:   diff - %p/Inputs/visualize-jumps-arm-ascii.txt
+
+test_func:
+  // Relocated instructions don't get control-flow edges.
+  bl extern_func
+  b extern_func
+  
+  // Two branches to the same label, one forward and one backward.
+  b .Llabel1
+.Llabel1:
+  nop
+  b .Llabel1
+
+  // Branch to self, no CFG edge shown
+  b .
+
+  // Conditional branches
+  beq .Llabel2
+.Llabel2:
+  nop
+
+  // Branches are sorted with shorter ones to the right, to reduce number of
+  // crossings, and keep the lines for short branches short themselves.
+  b .Llabel5
+  b .Llabel4
+  b .Llabel3
+.Llabel3:
+  nop
+.Llabel4:
+  nop
+.Llabel5:
+  nop
+
+  // Sometimes crossings can't be avoided.
+  b .Llabel6
+  b .Llabel7
+.Llabel6:
+  nop
+.Llabel7:
+  nop
+
+  // TODO If a branch goes to another branch instruction, we don't have a way
+  // to represent that. Can we improve on this?
+  b .Llabel8
+.Llabel8:
+  b .Llabel9
+.Llabel9:
+  nop
diff --git a/llvm/test/tools/llvm-objdump/visualize-jumps-thumb.s b/llvm/test/tools/llvm-objdump/visualize-jumps-thumb.s
new file mode 100644
index 00000000000000..8be73d3aa4c946
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/visualize-jumps-thumb.s
@@ -0,0 +1,63 @@
+// RUN: llvm-mc < %s -triple thumbv8a -filetype=obj | \
+// RUN:   llvm-objdump --triple thumbv8a -d --visualize-jumps=unicode - | \
+// RUN:   diff - %p/Inputs/visualize-jumps-thumb-unicode.txt
+
+// RUN: llvm-mc < %s -triple thumbv8a -filetype=obj | \
+// RUN:   llvm-objdump --triple thumbv8a -d --visualize-jumps=ascii - | \
+// RUN:   diff - %p/Inputs/visualize-jumps-thumb-ascii.txt
+
+test_func:
+  // Relocated instructions don't get control-flow edges.
+  bl extern_func
+  b extern_func
+  
+  // Two branches to the same label, one forward and one backward.
+  b .Llabel1
+.Llabel1:
+  nop
+  b .Llabel1
+
+  // Branch to self, no CFG edge shown
+  b .
+
+  // Different branch instructions
+  b.w .Llabel2
+  beq .Llabel2
+  bne.w .Llabel2
+  cbz r0, .Llabel2
+  it le
+  ble .Llabel2
+  nop
+.Llabel2:
+  nop
+.Llabel2.1:
+  nop
+
+  // Branches are sorted with shorter ones to the right, to reduce number of
+  // crossings, and keep the lines for short branches short themselves.
+  b .Llabel5
+  b .Llabel4
+  b .Llabel3
+.Llabel3:
+  nop
+.Llabel4:
+  nop
+.Llabel5:
+  nop
+
+  // Sometimes crossings can't be avoided.
+  b .Llabel6
+  b .Llabel7
+.Llabel6:
+  nop
+.Llabel7:
+  nop
+
+  // TODO If a branch goes to another branch instruction, we don't have a way
+  // to represent that. Can we improve on this?
+  b .Llabel8
+.Llabel8:
+  b .Llabel9
+.Llabel9:
+  nop
+
diff --git a/llvm/tools/llvm-objdump/ObjdumpOpts.td b/llvm/tools/llvm-objdump/ObjdumpOpts.td
index 100a95d3d92542..9a43cdaedd4626 100644
--- a/llvm/tools/llvm-objdump/ObjdumpOpts.td
+++ b/llvm/tools/llvm-objdump/ObjdumpOpts.td
@@ -92,6 +92,13 @@ def disassembler_color_EQ : Joined<["--"], "disassembler-color=">,
   HelpText<"Enable or disable disassembler color output. "
            "Valid options are \"on\", \"off\" and \"terminal\" (default)">;
 
+def visualize_jumps : Flag<["--"], "visualize-jumps">;
+def visualize_jumps_EQ : Joined<["--"], "visualize-jumps=">,
+  MetaVarName<"mode,...">,
+  HelpText<"Print a control flow graph along side disassembly. "
+           "Color modes: auto (default), nocolor, color. "
+           "Character modes: unicode (default), ascii.">;
+
 def dynamic_reloc : Flag<["--"], "dynamic-reloc">,
   HelpText<"Display the dynamic relocation entries in the file">;
 def : Flag<["-"], "R">, Alias<dynamic_reloc>,
diff --git a/llvm/tools/llvm-objdump/SourcePrinter.cpp b/llvm/tools/llvm-objdump/SourcePrinter.cpp
index 31101bccf4ae60..0d4f68b214ef80 100644
--- a/llvm/tools/llvm-objdump/SourcePrinter.cpp
+++ b/llvm/tools/llvm-objdump/SourcePrinter.cpp
@@ -262,12 +262,15 @@ void LiveVariablePrinter::printAfterOtherLine(formatted_raw_ostream &OS,
 /// already-active live ranges) because something has already been printed
 /// earlier on this line.
 void LiveVariablePrinter::printBetweenInsts(formatted_raw_ostream &OS,
-                                            bool MustPrint) {
+                                            bool MustPrint, uint64_t Addr,
+                                            ControlFlowPrinter *CFP) {
   bool PrintedSomething = false;
   for (unsigned ColIdx = 0, End = ActiveCols.size(); ColIdx < End; ++ColIdx) {
     if (ActiveCols[ColIdx].isActive() && ActiveCols[ColIdx].MustDrawLabel) {
       // First we need to print the live range markers for any active
       // columns to the left of this one.
+      if (CFP)
+        CFP->printOther(OS, Addr);
       OS.PadToColumn(getIndentLevel());
       for (unsigned ColIdx2 = 0; ColIdx2 < ColIdx; ++ColIdx2) {
         if (ActiveCols[ColIdx2].isActive()) {
@@ -503,5 +506,194 @@ SourcePrinter::SourcePrinter(const object::ObjectFile *Obj,
   Symbolizer.reset(new symbolize::LLVMSymbolizer(SymbolizerOpts));
 }
 
+// TODO Light/dark shades? 256-color terminals?
+const raw_ostream::Colors LineColors[] = {
+  raw_ostream::RED,
+  raw_ostream::GREEN,
+  raw_ostream::YELLOW,
+  raw_ostream::BLUE,
+  raw_ostream::MAGENTA,
+  raw_ostream::CYAN,
+};
+
+void ControlFlowPrinter::addEdge(uint64_t From, uint64_t To) {
+  auto It = Targets.find(To);
+  if (It == Targets.end())
+    It = Targets.insert(std::make_pair(To, ControlFlowTarget(To, PickColor()))).first;
+  It->second.addSource(From);
+}
+
+void ControlFlowPrinter::finalise() {
+  if (!enabled()) {
+    setControlFlowColumnWidth(0);
+    return;
+  }
+
+  SmallVector<ControlFlowTarget *> SortedTargets;
+  for (auto &[Addr, Info] : Targets) {
+    SortedTargets.push_back(&Info);
+  }
+  std::sort(SortedTargets.begin(), SortedTargets.end(),
+            [](ControlFlowTarget *LHS, ControlFlowTarget *RHS) {
+              return LHS->Length() < RHS->Length();
+            });
+
+  // FIXME This is O(n^3) in the worst case, can we do better?
+  for (auto &T : SortedTargets) {
+    int Column = 0;
+    for (;; ++Column)
+      if (!std::any_of(SortedTargets.begin(), SortedTargets.end(),
+                       [T, Column](ControlFlowTarget *T2) {
+                         return T != T2 && T2->Column == Column &&
+                                T->Overlaps(*T2);
+                       }))
+        break;
+    T->Column = Column;
+    MaxColumn = std::max(MaxColumn, Column);
+
+#if 1
+    LLVM_DEBUG(
+      dbgs() << "Target: 0x" << Twine::utohexstr(T->Target) << " (" << T->Length()
+             << ") Column " << Column << ":\n";
+      for (auto Source : T->Sources)
+        dbgs() << "  Source: 0x" << Twine::utohexstr(Source) << "\n";
+    );
+#endif
+  }
+
+  setControlFlowColumnWidth(MaxColumn * 2 + 4);
+}
+
+const char *ControlFlowPrinter::getLineChar(LineChar C) const {
+  bool IsASCII =
+      (OutputMode & VisualizeJumpsMode::CharsMask) == VisualizeJumpsMode::CharsASCII;
+  switch (C) {
+  case LineChar::Horiz:
+    return IsASCII ? "-" : (const char *)u8"\u2500";
+  case LineChar::Vert:
+    return IsASCII ? "|" : (const char *)u8"\u2502";
+  case LineChar::TopCorner:
+    return IsASCII ? "/" : (const char *)u8"\u256d";
+  case LineChar::BottomCorner:
+    return IsASCII ? "\\" : (const char *)u8"\u2570";
+  case LineChar::Tee:
+    return IsASCII ? "+" : (const char *)u8"\u251c";
+  case LineChar::Arrow:
+    return ">";
+  }
+  llvm_unreachable("Unhandled LineChar enum");
+}
+
+#define C(id) getLineChar(LineChar::id)
+
+void ControlFlowPrinter::printInst(formatted_raw_ostream &OS,
+                                   uint64_t Addr) const {
+  if (!enabled())
+    return;
+
+  SmallVector<const ControlFlowTarget *, 8> Columns;
+  Columns.resize(MaxColumn + 1);
+  const ControlFlowTarget *Horizontal = nullptr;
+
+  IndentToColumn(STI, OS, DisassemblyColumn::ControlFlow);
+
+  // TODO: What happens if an instruction has both incoming and outgoing edges?
+
+  for (auto &[_, Info] : Targets) {
+    if (Info.ActiveAt(Addr)) {
+      assert(Columns[Info.Column] == nullptr);
+      Columns[Info.Column] = &Info;
+    }
+  }
+
+  auto Color = [&](raw_ostream &OS,
+                   raw_ostream::Colors Color) -> raw_ostream & {
+    if ((OutputMode & VisualizeJumpsMode::ColorMask) !=
+        VisualizeJumpsMode::Off) {
+      OS << Color;
+    }
+    return OS;
+  };
+
+  OS.PadToColumn(getIndentLevel());
+  for (int ColIdx = MaxColumn; ColIdx >= 0; --ColIdx) {
+    if (Horizontal) {
+      if (Columns[ColIdx]) {
+        // Outgoing horizontal lines are drawn "under" vertical line, because
+        // that minimises the gap in the "lower" line.
+        Color(OS, Horizontal->Color) << C(Horiz);
+        Color(OS, Columns[ColIdx]->Color) << C(Vert);
+      } else
+        Color(OS, Horizontal->Color) << C(Horiz) << C(Horiz);
+    } else if (!Columns[ColIdx])
+      OS << "  ";
+    else if (Columns[ColIdx]->HorizontalAt(Addr)) {
+      Horizontal = Columns[ColIdx];
+      if (Columns[ColIdx]->StartsAt(Addr))
+        Color(OS, Horizontal->Color) << " " << C(TopCorner);
+      else if (Columns[ColIdx]->EndsAt(Addr))
+        Color(OS, Horizontal->Color) << " " << C(BottomCorner);
+      else
+        Color(OS, Horizontal->Color) << " " << C(Tee);
+    } else if (Columns[ColIdx]->ActiveAt(Addr))
+      Color(OS, Columns[ColIdx]->Color) << " " << C(Vert);
+    else
+      OS << "  ";
+  }
+
+  if (Horizontal) {
+    if (Horizontal->TargetAt(Addr))
+      Color(OS, Horizontal->Color) << C(Horiz) << C(Arrow);
+    else
+      Color(OS, Horizontal->Color) << C(Horiz) << C(Horiz);
+  } else {
+    OS << "  ";
+  }
+
+  Color(OS, raw_ostream::RESET);
+}
+
+// TODO boolean params -> enum?
+void ControlFlowPrinter::printOther(formatted_raw_ostream &OS, uint64_t Addr,
+                                    bool BeforeInst, bool AfterInst) const {
+  if (!enabled())
+    return;
+
+  assert(!(BeforeInst && AfterInst));
+
+  SmallVector<const ControlFlowTarget *, 8> Columns;
+  Columns.resize(MaxColumn + 1);
+
+  auto Color = [&](raw_ostream &OS,
+                   raw_ostream::Colors Color) -> raw_ostream & {
+    if ((OutputMode & VisualizeJumpsMode::ColorMask) !=
+        VisualizeJumpsMode::Off) {
+      OS << Color;
+    }
+    return OS;
+  };
+
+  IndentToColumn(STI, OS, DisassemblyColumn::ControlFlow);
+
+  for (auto &[_, Info] : Targets) {
+    if (Info.ActiveAt(Addr, BeforeInst, AfterInst)) {
+      assert(Columns[Info.Column] == nullptr);
+      Columns[Info.Column] = &Info;
+    }
+  }
+
+  OS.PadToColumn(getIndentLevel());
+  for (int ColIdx = MaxColumn; ColIdx >= 0; --ColIdx) {
+    if (!Columns[ColIdx])
+      OS << "  ";
+    else
+      Color(OS, Columns[ColIdx]->Color) << " " << C(Vert);
+  }
+
+  Color(OS, raw_ostream::RESET) << "  ";
+}
+
+#undef C
+
 } // namespace objdump
 } // namespace llvm
diff --git a/llvm/tools/llvm-objdump/SourcePrinter.h b/llvm/tools/llvm-objdump/SourcePrinter.h
index fc67fc65074441..c311277121a620 100644
--- a/llvm/tools/llvm-objdump/SourcePrinter.h
+++ b/llvm/tools/llvm-objdump/SourcePrinter.h
@@ -22,6 +22,8 @@
 namespace llvm {
 namespace objdump {
 
+class ControlFlowPrinter;
+
 /// Stores a single expression representing the location of a source-level
 /// variable, along with the PC range for which that expression is valid.
 struct LiveVariable {
@@ -121,7 +123,8 @@ class LiveVariablePrinter {
   /// true, we have to print at least one line (with the continuation of any
   /// already-active live ranges) because something has already been printed
   /// earlier on this line.
-  void printBetweenInsts(formatted_raw_ostream &OS, bool MustPrint);
+  void printBetweenInsts(formatted_raw_ostream &OS, bool MustPrint,
+                         uint64_t Addr = 0, ControlFlowPrinter *CFP = nullptr);
 
   /// Print the live variable ranges to the right of a disassembled instruction.
   void printAfterInst(formatted_raw_ostream &OS);
@@ -166,6 +169,114 @@ class SourcePrinter {
                                StringRef Delimiter = "; ");
 };
 
+enum VisualizeJumpsMode : int {
+  Off = 0,
+
+  ColorAuto = 0x1,
+  Color3Bit,
+  //Color8Bit, // TODO GNU objdump can use more colors in 256-color terminals
+
+  CharsASCII = 0x10,
+  CharsUnicode = 0x20,
+
+  ColorMask = 0xf,
+  CharsMask = 0xf0,
+};
+
+
+extern const raw_ostream::Colors LineColors[6];
+
+class ControlFlowPrinter {
+  struct ControlFlowTarget {
+    uint64_t Target;
+    SmallVector<uint64_t, 4> Sources;
+    int Column;
+    raw_ostream::Colors Color;
+
+    ControlFlowTarget(uint64_t Target, raw_ostream::Colors Color)
+        : Target(Target), Column(~0U), Color(Color), High(Target), Low(Target) {}
+    ControlFlowTarget(const ControlFlowTarget &) = delete;
+    ControlFlowTarget(ControlFlowTarget &&) = default;
+
+    void addSource(uint64_t Source) {
+      Sources.push_back(Source);
+      Low = std::min(Low, Source);
+      High = std::max(High, Source);
+    }
+
+    uint64_t Length() const { return High - Low; }
+
+    bool Overlaps(ControlFlowTarget &Other) const {
+      return !(Other.Low > High || Other.High < Low);
+    }
+
+    bool ActiveAt(uint64_t Addr, bool BeforeInst = false,
+                  bool AfterInst = false) const {
+      if (BeforeInst)
+        return Addr > Low && Addr <= High;
+      else if (AfterInst)
+        return Addr >= Low && Addr < High;
+      else
+        return Addr >= Low && Addr <= High;
+    }
+
+    bool StartsAt(uint64_t Addr) const { return Addr == Low; }
+    bool EndsAt(uint64_t Addr) const { return Addr == High; }
+    bool TargetAt(uint64_t Addr) const { return Addr == Target; }
+
+    bool HorizontalAt(uint64_t Addr) const {
+      return Addr == Target ||
+             std::any_of(Sources.begin(), Sources.end(),
+                         [Addr](uint64_t Src) { return Src == Addr; });
+    }
+
+  private:
+    uint64_t High, Low;
+  };
+
+  VisualizeJumpsMode OutputMode;
+  DenseMap<uint64_t, ControlFlowTarget> Targets;
+  int MaxColumn;
+  const MCSubtargetInfo &STI;
+
+  int NextColorIdx;
+  raw_ostream::Colors PickColor() {
+    if ((OutputMode & VisualizeJumpsMode::ColorMask) ==
+        VisualizeJumpsMode::Off)
+      return raw_ostream::RESET;
+    auto Ret = LineColors[NextColorIdx];
+    NextColorIdx = (NextColorIdx + 1) % (sizeof(LineColors) / sizeof(LineColors[0]));
+    return Ret;
+  }
+
+  bool enabled() const { return OutputMode != VisualizeJumpsMode::Off; }
+  int getIndentLevel() const { return 10; }
+
+  enum class LineChar {
+    Horiz,
+    Vert,
+    TopCorner,
+    BottomCorner,
+    Tee,
+    Arrow,
+  };
+  const char *getLineChar(LineChar C) const;
+
+public:
+  ControlFlowPrinter(VisualizeJumpsMode OutputMode, const MCSubtargetInfo &STI)
+      : OutputMode(OutputMode), MaxColumn(0), STI(STI), NextColorIdx(0) {}
+
+  // Add a control-flow edge from the instruction at address From to the
+  // instruction at address To.
+  void addEdge(uint64_t From, uint64_t To);
+
+  void finalise();
+
+  void printInst(formatted_raw_ostream &OS, uint64_t Addr) const;
+  void printOther(formatted_raw_ostream &OS, uint64_t Addr,
+                  bool BeforeInst = false, bool AfterInst = false) const;
+};
+
 } // namespace objdump
 } // namespace llvm
 
diff --git a/llvm/tools/llvm-objdump/llvm-objdump.cpp b/llvm/tools/llvm-objdump/llvm-objdump.cpp
index 80604bff825664..9cfd1260721efc 100644
--- a/llvm/tools/llvm-objdump/llvm-objdump.cpp
+++ b/llvm/tools/llvm-objdump/llvm-objdump.cpp
@@ -73,6 +73,7 @@
 #include "llvm/Support/GraphWriter.h"
 #include "llvm/Support/InitLLVM.h"
 #include "llvm/Support/LLVMDriver.h"
+#include "llvm/Support/MathExtras.h"
 #include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/SourceMgr.h"
 #include "llvm/Support/StringSaver.h"
@@ -197,6 +198,7 @@ static std::vector<std::string> DisassembleSymbols;
 static bool DisassembleZeroes;
 static std::vector<std::string> DisassemblerOptions;
 static ColorOutput DisassemblyColor;
+static VisualizeJumpsMode VisualizeJumps;
 DIDumpType objdump::DwarfDumpType;
 static bool DynamicRelocations;
 static bool FaultMapSection;
@@ -471,10 +473,9 @@ static bool getHidden(RelocationRef RelRef) {
   return false;
 }
 
-/// Get the column at which we want to start printing the instruction
-/// disassembly, taking into account anything which appears to the left of it.
-unsigned objdump::getInstStartColumn(const MCSubtargetInfo &STI) {
-  return !ShowRawInsn ? 16 : STI.getTargetTriple().isX86() ? 40 : 24;
+static int ControlFlowColumnWidth = 0;
+void objdump::setControlFlowColumnWidth(int Width) {
+  ControlFlowColumnWidth = Width;
 }
 
 unsigned EncodingColumnWidth(Triple const &Triple) {
@@ -497,6 +498,12 @@ unsigned objdump::GetColumnIndent(MCSubtargetInfo const &STI,
   // Address: 8 chars of address, followed by ": "
   Indent += 10;
 
+  if (Col == DisassemblyColumn::ControlFlow)
+    return Indent;
+
+  // Control-flow graph: depends on function, disabled by default.
+  Indent += ControlFlowColumnWidth;
+
   if (Col == DisassemblyColumn::Encoding)
     return Indent;
 
@@ -615,12 +622,20 @@ class PrettyPrinter {
             object::SectionedAddress Address, formatted_raw_ostream &OS,
             StringRef Annot, MCSubtargetInfo const &STI, SourcePrinter *SP,
             StringRef ObjectFilename, std::vector<RelocationRef> *Rels,
-            LiveVariablePrinter &LVP) {
+            LiveVariablePrinter &LVP, ControlFlowPrinter &CFP) {
     if (SP && (PrintSource || PrintLines))
       SP->printSourceLine(OS, Address, ObjectFilename, LVP);
-    LVP.printBetweenInsts(OS, false);
+    LVP.printBetweenInsts(OS, false, Address.Address, &CFP);
 
-    printRawData(Bytes, Address.Address, OS, STI);
+    if (LeadingAddr)
+      OS << format("%8" PRIx64 ":", Address.Address);
+
+    CFP.printInst(OS, Address.Address);
+
+    if (ShowRawInsn) {
+      OS << ' ';
+      dumpBytes(Bytes, OS);
+    }
 
     IndentToColumn(STI, OS, DisassemblyColumn::Assembly);
 
@@ -656,7 +671,7 @@ class HexagonPrettyPrinter : public PrettyPrinter {
                  object::SectionedAddress Address, formatted_raw_ostream &OS,
                  StringRef Annot, MCSubtargetInfo const &STI, SourcePrinter *SP,
                  StringRef ObjectFilename, std::vector<RelocationRef> *Rels,
-                 LiveVariablePrinter &LVP) override {
+                 LiveVariablePrinter &LVP, ControlFlowPrinter &CFP) override {
     if (SP && (PrintSource || PrintLines))
       SP->printSourceLine(OS, Address, ObjectFilename, LVP, "");
     if (!MI) {
@@ -726,7 +741,7 @@ class AMDGCNPrettyPrinter : public PrettyPrinter {
                  object::SectionedAddress Address, formatted_raw_ostream &OS,
                  StringRef Annot, MCSubtargetInfo const &STI, SourcePrinter *SP,
                  StringRef ObjectFilename, std::vector<RelocationRef> *Rels,
-                 LiveVariablePrinter &LVP) override {
+                 LiveVariablePrinter &LVP, ControlFlowPrinter &CFP) override {
     if (SP && (PrintSource || PrintLines))
       SP->printSourceLine(OS, Address, ObjectFilename, LVP);
 
@@ -779,7 +794,7 @@ class BPFPrettyPrinter : public PrettyPrinter {
                  object::SectionedAddress Address, formatted_raw_ostream &OS,
                  StringRef Annot, MCSubtargetInfo const &STI, SourcePrinter *SP,
                  StringRef ObjectFilename, std::vector<RelocationRef> *Rels,
-                 LiveVariablePrinter &LVP) override {
+                 LiveVariablePrinter &LVP, ControlFlowPrinter &CFP) override {
     if (SP && (PrintSource || PrintLines))
       SP->printSourceLine(OS, Address, ObjectFilename, LVP);
     if (LeadingAddr)
@@ -802,13 +817,16 @@ class ARMPrettyPrinter : public PrettyPrinter {
                  object::SectionedAddress Address, formatted_raw_ostream &OS,
                  StringRef Annot, MCSubtargetInfo const &STI, SourcePrinter *SP,
                  StringRef ObjectFilename, std::vector<RelocationRef> *Rels,
-                 LiveVariablePrinter &LVP) override {
+                 LiveVariablePrinter &LVP, ControlFlowPrinter &CFP) override {
     if (SP && (PrintSource || PrintLines))
       SP->printSourceLine(OS, Address, ObjectFilename, LVP);
-    LVP.printBetweenInsts(OS, false);
+    LVP.printBetweenInsts(OS, false, Address.Address, &CFP);
 
     if (LeadingAddr)
       OS << format("%8" PRIx64 ":", Address.Address);
+
+    CFP.printInst(OS, Address.Address);
+
     if (ShowRawInsn) {
       size_t Pos = 0, End = Bytes.size();
       if (STI.checkFeatures("+thumb-mode")) {
@@ -855,13 +873,16 @@ class AArch64PrettyPrinter : public PrettyPrinter {
                  object::SectionedAddress Address, formatted_raw_ostream &OS,
                  StringRef Annot, MCSubtargetInfo const &STI, SourcePrinter *SP,
                  StringRef ObjectFilename, std::vector<RelocationRef> *Rels,
-                 LiveVariablePrinter &LVP) override {
+                 LiveVariablePrinter &LVP, ControlFlowPrinter &CFP) override {
     if (SP && (PrintSource || PrintLines))
       SP->printSourceLine(OS, Address, ObjectFilename, LVP);
-    LVP.printBetweenInsts(OS, false);
+    LVP.printBetweenInsts(OS, false, Address.Address, &CFP);
 
     if (LeadingAddr)
       OS << format("%8" PRIx64 ":", Address.Address);
+
+    CFP.printInst(OS, Address.Address);
+
     if (ShowRawInsn) {
       size_t Pos = 0, End = Bytes.size();
       for (; Pos + 4 <= End; Pos += 4)
@@ -1343,7 +1364,9 @@ collectLocalBranchTargets(ArrayRef<uint8_t> Bytes, MCInstrAnalysis *MIA,
                           MCDisassembler *DisAsm, MCInstPrinter *IP,
                           const MCSubtargetInfo *STI, uint64_t SectionAddr,
                           uint64_t Start, uint64_t End,
-                          std::unordered_map<uint64_t, std::string> &Labels) {
+                          std::unordered_map<uint64_t, std::string> &Labels,
+                          ControlFlowPrinter &CFP,
+                          std::vector<RelocationRef> &Relocs) {
   if (MIA)
     MIA->resetState();
 
@@ -1352,6 +1375,10 @@ collectLocalBranchTargets(ArrayRef<uint8_t> Bytes, MCInstrAnalysis *MIA,
   Start += SectionAddr;
   End += SectionAddr;
   uint64_t Index = Start;
+
+  std::vector<RelocationRef>::const_iterator RelCur = Relocs.begin();
+  std::vector<RelocationRef>::const_iterator RelEnd = Relocs.end();
+
   while (Index < End) {
     // Disassemble a real instruction and record function-local branch labels.
     MCInst Inst;
@@ -1363,15 +1390,37 @@ collectLocalBranchTargets(ArrayRef<uint8_t> Bytes, MCInstrAnalysis *MIA,
       Size = std::min<uint64_t>(ThisBytes.size(),
                                 DisAsm->suggestBytesToSkip(ThisBytes, Index));
 
+    // Check for relocations which apply to this instruction.
+    bool Relocated = false;
+    while (RelCur != RelEnd) {
+      // FIXME RelAdjustment for executables & shared objects
+      uint64_t Offset = RelCur->getOffset(); // - RelAdjustment;
+
+      if (Offset >= Index + Size)
+        break;
+
+      Relocated = true;
+      ++RelCur;
+    }
+
     if (Disassembled && MIA) {
       uint64_t Target;
       bool TargetKnown = MIA->evaluateBranch(Inst, Index, Size, Target);
-      // On PowerPC, if the address of a branch is the same as the target, it
-      // means that it's a function call. Do not mark the label for this case.
-      if (TargetKnown && (Target >= Start && Target < End) &&
-          !Labels.count(Target) &&
-          !(STI->getTargetTriple().isPPC() && Target == Index))
-        Labels[Target] = ("L" + Twine(LabelCount++)).str();
+      if (TargetKnown && (Target >= Start && Target < End)) {
+        // On PowerPC and ARM, if the address of a branch is the same as the
+        // target, it means that it's a function call. Do not mark the label for
+        // this case.
+        if (!Labels.count(Target) &&
+            !((STI->getTargetTriple().isPPC() ||
+               STI->getTargetTriple().isARM()) &&
+              Target == Index) &&
+            SymbolizeOperands) {
+          Labels[Target] = ("L" + Twine(LabelCount++)).str();
+        }
+
+        if (Target != Index && !Relocated)
+          CFP.addEdge(Index, Target);
+      }
       MIA->updateState(Inst, Index);
     } else if (!Disassembled && MIA) {
       MIA->resetState();
@@ -1460,9 +1509,10 @@ static void emitPostInstructionInfo(formatted_raw_ostream &FOS,
       StringRef Comment;
       std::tie(Comment, Comments) = Comments.split('\n');
       // MAI.getCommentColumn() assumes that instructions are printed at the
-      // position of 8, while getInstStartColumn() returns the actual position.
+      // position of 8, while GetColumnIndent() returns the actual position.
       unsigned CommentColumn =
-          MAI.getCommentColumn() - 8 + getInstStartColumn(STI);
+          MAI.getCommentColumn() - 8 +
+          GetColumnIndent(STI, DisassemblyColumn::Assembly);
       FOS.PadToColumn(CommentColumn);
       FOS << MAI.getCommentString() << ' ' << Comment;
     }
@@ -1538,7 +1588,7 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj,
   }
 
   std::map<SectionRef, std::vector<RelocationRef>> RelocMap;
-  if (InlineRelocs)
+  if (InlineRelocs || VisualizeJumps)
     RelocMap = getRelocsMap(Obj);
   bool Is64Bits = Obj.getBytesInAddress() > 4;
 
@@ -2019,15 +2069,20 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj,
 
       std::unordered_map<uint64_t, std::string> AllLabels;
       std::unordered_map<uint64_t, std::vector<std::string>> BBAddrMapLabels;
+      ControlFlowPrinter CFP(VisualizeJumps, *DT->SubtargetInfo);
+      if (SymbolizeOperands || VisualizeJumps) {
+        collectLocalBranchTargets(
+            Bytes, DT->InstrAnalysis.get(), DT->DisAsm.get(),
+            DT->InstPrinter.get(), PrimaryTarget.SubtargetInfo.get(),
+            SectionAddr, Index, End, AllLabels, CFP, Rels);
+      }
       if (SymbolizeOperands) {
-        collectLocalBranchTargets(Bytes, DT->InstrAnalysis.get(),
-                                  DT->DisAsm.get(), DT->InstPrinter.get(),
-                                  PrimaryTarget.SubtargetInfo.get(),
-                                  SectionAddr, Index, End, AllLabels);
         collectBBAddrMapLabels(AddrToBBAddrMap, SectionAddr, Index, End,
                                BBAddrMapLabels);
       }
 
+      CFP.finalise();
+
       if (DT->InstrAnalysis)
         DT->InstrAnalysis->resetState();
 
@@ -2076,7 +2131,7 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj,
             uint64_t MaxOffset = End - Index;
             // For --reloc: print zero blocks patched by relocations, so that
             // relocations can be shown in the dump.
-            if (RelCur != RelEnd)
+            if (InlineRelocs && RelCur != RelEnd)
               MaxOffset = std::min(RelCur->getOffset() - RelAdjustment - Index,
                                    MaxOffset);
 
@@ -2103,12 +2158,14 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj,
           if (Iter1 != BBAddrMapLabels.end()) {
             for (StringRef Label : Iter1->second) {
               FOS << "<" << Label << ">:";
+              CFP.printOther(FOS, Index, true);
               LVP.printAfterOtherLine(FOS, true);
             }
           } else {
             auto Iter2 = AllLabels.find(SectionAddr + Index);
             if (Iter2 != AllLabels.end()) {
               FOS << "<" << Iter2->second << ">:";
+              CFP.printOther(FOS, Index, true);
               LVP.printAfterOtherLine(FOS, true);
             }
           }
@@ -2134,7 +2191,7 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj,
               *DT->InstPrinter, Disassembled ? &Inst : nullptr,
               Bytes.slice(Index, Size),
               {SectionAddr + Index + VMAAdjustment, Section.getIndex()}, FOS,
-              "", *DT->SubtargetInfo, &SP, Obj.getFileName(), &Rels, LVP);
+              "", *DT->SubtargetInfo, &SP, Obj.getFileName(), &Rels, LVP, CFP);
 
           DT->InstPrinter->setCommentStream(llvm::nulls());
 
@@ -2267,7 +2324,7 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj,
           printBTFRelocation(FOS, *BTF, {Index, Section.getIndex()}, LVP);
 
         // Hexagon does this in pretty printer
-        if (Obj.getArch() != Triple::hexagon) {
+        if (Obj.getArch() != Triple::hexagon && InlineRelocs) {
           // Print relocation for instruction and data.
           while (RelCur != RelEnd) {
             uint64_t Offset = RelCur->getOffset() - RelAdjustment;
@@ -2294,6 +2351,7 @@ disassembleObject(ObjectFile &Obj, const ObjectFile &DbgObj,
                 Offset += AdjustVMA;
             }
 
+            CFP.printOther(FOS, Index, false, true);
             printRelocation(FOS, Obj.getFileName(), *RelCur,
                             SectionAddr + Offset, Is64Bits);
             LVP.printAfterOtherLine(FOS, true);
@@ -3336,6 +3394,45 @@ static void parseObjdumpOptions(const llvm::opt::InputArgList &InputArgs) {
     if (DisassemblyColor == ColorOutput::Invalid)
       invalidArgValue(A);
   }
+  if (const opt::Arg *A = InputArgs.getLastArg(OBJDUMP_visualize_jumps, OBJDUMP_visualize_jumps_EQ)) {
+    if (A->getOption().matches(OBJDUMP_visualize_jumps)) {
+      // --visualize-jumps without an argument default to unicode, auto-color.
+      VisualizeJumps = (VisualizeJumpsMode)(VisualizeJumpsMode::CharsUnicode |
+                                            VisualizeJumpsMode::ColorAuto);
+    } else {
+      SmallVector<StringRef, 2> Parts;
+      StringRef(A->getValue()).split(Parts, ",");
+      VisualizeJumpsMode Color = VisualizeJumpsMode::ColorAuto;
+      VisualizeJumpsMode Chars = VisualizeJumpsMode::CharsUnicode;
+
+      for (StringRef Part : Parts) {
+        if (Part == "off") {
+          Color = VisualizeJumpsMode::Off;
+          Chars = VisualizeJumpsMode::Off;
+        } else if (Part == "nocolor") {
+          Color = VisualizeJumpsMode::Off;
+        } else if (Part == "auto") {
+          Color = VisualizeJumpsMode::ColorAuto;
+        } else if (Part == "color") {
+          Color = VisualizeJumpsMode::Color3Bit;
+        } else if (Part == "ascii") {
+          Chars = VisualizeJumpsMode::CharsASCII;
+        } else if (Part == "unicode") {
+          Chars = VisualizeJumpsMode::CharsUnicode;
+        } else {
+          reportCmdLineError("'" + Part + "' is not a valid value for '" +
+                             A->getSpelling() + "'");
+        }
+      }
+
+      if (Color == VisualizeJumpsMode::ColorAuto) {
+        Color = outs().has_colors() ? VisualizeJumpsMode::Color3Bit
+                                    : VisualizeJumpsMode::Off;
+      }
+
+      VisualizeJumps = (VisualizeJumpsMode)(Color | Chars);
+    }
+  }
 
   parseIntArg(InputArgs, OBJDUMP_debug_vars_indent_EQ, DbgIndent);
 
diff --git a/llvm/tools/llvm-objdump/llvm-objdump.h b/llvm/tools/llvm-objdump/llvm-objdump.h
index 31281ba3d8c2ad..74c142aa80ed92 100644
--- a/llvm/tools/llvm-objdump/llvm-objdump.h
+++ b/llvm/tools/llvm-objdump/llvm-objdump.h
@@ -146,11 +146,13 @@ void printRawData(llvm::ArrayRef<uint8_t> Bytes, uint64_t Address,
 
 enum class DisassemblyColumn {
   Address,
+  ControlFlow,
   Encoding,
   Assembly,
   Variables,
 };
 
+void setControlFlowColumnWidth(int Width);
 unsigned GetColumnIndent(MCSubtargetInfo const &STI, DisassemblyColumn Col);
 void IndentToColumn(MCSubtargetInfo const &STI, formatted_raw_ostream &OS,
                     DisassemblyColumn Col);



More information about the llvm-commits mailing list