[llvm] Add flags to dump IR to a file before and after LLVM passes (PR #65179)

Nuri Amari via llvm-commits llvm-commits at lists.llvm.org
Fri Sep 1 13:52:21 PDT 2023


https://github.com/NuriAmari updated https://github.com/llvm/llvm-project/pull/65179:

>From 5d395c85b84e5a554df4b092014d38123e666c6c Mon Sep 17 00:00:00 2001
From: Nuri Amari <nuriamari at fb.com>
Date: Tue, 29 Aug 2023 10:10:57 -0700
Subject: [PATCH] Add flags to dump IR to a file before and after LLVM passes

Summary:

LLVM offers -print-after and -print-before flags that allow you
to print IR to stderr before and after any pass you want. This can
be useful for debugging LLVM optimization issue, but is far too
noisy for large builds.

This patch adds analogous options -dump-after and -dump-before that
dump the IR to appropriately named files. In addition, it also
introduces flags -dump-after-all, -dump-before-all and -ir-dump-directory
to control where the files are written to.

Test Plan:

Included LIT tests:
```
ninja check-llvm
```
---
 llvm/include/llvm/IR/PrintPasses.h            |  21 +++
 .../llvm/Passes/StandardInstrumentations.h    |  57 ++++++
 llvm/lib/IR/PrintPasses.cpp                   |  54 ++++++
 llvm/lib/Passes/PassBuilder.cpp               |   3 +-
 llvm/lib/Passes/StandardInstrumentations.cpp  | 178 ++++++++++++++++++
 .../Other/dump-before-after-file-contents     |  76 ++++++++
 llvm/test/Other/dump-before-after-filenames   |  71 +++++++
 .../Other/dump-before-after-multiple-modules  |  36 ++++
 llvm/test/Other/lit.local.cfg                 |   1 +
 9 files changed, 496 insertions(+), 1 deletion(-)
 create mode 100644 llvm/test/Other/dump-before-after-file-contents
 create mode 100644 llvm/test/Other/dump-before-after-filenames
 create mode 100644 llvm/test/Other/dump-before-after-multiple-modules
 create mode 100644 llvm/test/Other/lit.local.cfg

diff --git a/llvm/include/llvm/IR/PrintPasses.h b/llvm/include/llvm/IR/PrintPasses.h
index 95b97e76c867cb..b2a9017521c6a9 100644
--- a/llvm/include/llvm/IR/PrintPasses.h
+++ b/llvm/include/llvm/IR/PrintPasses.h
@@ -48,6 +48,27 @@ bool shouldPrintAfterAll();
 std::vector<std::string> printBeforePasses();
 std::vector<std::string> printAfterPasses();
 
+// Returns true if dumping IR to a file before/after some pass is enabled
+// wether all passes or a specific pass.
+bool shouldDumpBeforeSomePass();
+bool shouldDumpAfterSomePass();
+
+// Returns true if we should dump IR to a file before/after a specific pass. The
+// argument should be the pass ID, e.g. "instcombine"
+bool shouldDumpBeforePass(StringRef PassID);
+bool shouldDumpAfterPass(StringRef PassID);
+
+// Returns true if we should dump IR to a file before/after all passes.
+bool shouldDumpBeforeAll();
+bool shouldDumpAfterAll();
+
+// The list of passes to dump IR to a file before/after, if we only want
+// to print before/after specific passes.
+std::vector<std::string> dumpBeforePasses();
+std::vector<std::string> dumpAfterPasses();
+
+StringRef irInstrumentationDumpDirectory();
+
 // Returns true if we should always print the entire module.
 bool forcePrintModuleIR();
 
diff --git a/llvm/include/llvm/Passes/StandardInstrumentations.h b/llvm/include/llvm/Passes/StandardInstrumentations.h
index 331130c6b22d99..4068a68aa956fd 100644
--- a/llvm/include/llvm/Passes/StandardInstrumentations.h
+++ b/llvm/include/llvm/Passes/StandardInstrumentations.h
@@ -69,6 +69,62 @@ class PrintIRInstrumentation {
   unsigned CurrentPassNumber = 0;
 };
 
+class DumpIRInstrumentation {
+public:
+  void registerCallbacks(PassInstrumentationCallbacks &PIC);
+
+private:
+  void dumpBeforePass(StringRef PassID, Any IR);
+  void dumpAfterPass(StringRef PassID, Any IR);
+
+  bool shouldDumpBeforePass(StringRef PassID);
+  bool shouldDumpAfterPass(StringRef PassID);
+
+  PassInstrumentationCallbacks *PIC;
+
+  // The module currently being processed in the pipeline.
+  Module const *CurrentModule = nullptr;
+
+  void pushPass(StringRef PassID, Any IR);
+  void popPass(StringRef PassID);
+
+  SmallString<16> InstrumentationDumpDirectory;
+  StringRef fetchInstrumentationDumpDirectory();
+
+  SmallString<16> fetchCurrentInstrumentationDumpFile(StringRef Suffix);
+
+  // A table to store how many times a given pass has run at the current "nested
+  // level"
+  using PassRunsFrequencyTableT = DenseMap<StringRef, unsigned>;
+  // A stack each frame of which (aside from the very first) represents a pass
+  // being run on some unit of IR. The larger, the stack grows, the smaller the
+  // unit of IR. For example, we would first push a module pass, then for each
+  // function pass in that module pass, we would push a frame and so on. This
+  // information is used to craft the output path for this logging.
+  //
+  // Each frame contains a map to track how many times a given subpass runs. For
+  // example, to keep track of how many times a function pass Foo runs within a
+  // module pass Bar. The first frame of the stack represents the module being
+  // processed rather than any particular pass. This is to create a frequency
+  // table to track module level pass run counts without having to special case
+  // that logic.
+  //
+  // When a change in the module being processed is detected, this first frame
+  // is updated accordingly.
+
+  struct PipelineStateStackFrame {
+    StringRef PassID;
+    PassRunsFrequencyTableT FreqTable;
+    unsigned int PassCount;
+
+    PipelineStateStackFrame(StringRef PassID) : PassID{PassID}, PassCount{0} {}
+  };
+
+  using PipelineStateStackT = SmallVector<PipelineStateStackFrame>;
+
+  PipelineStateStackT PipelineStateStack;
+};
+
 class OptNoneInstrumentation {
 public:
   OptNoneInstrumentation(bool DebugLogging) : DebugLogging(DebugLogging) {}
@@ -555,6 +611,7 @@ class PrintCrashIRInstrumentation {
 /// instrumentations and manages their state (if any).
 class StandardInstrumentations {
   PrintIRInstrumentation PrintIR;
+  DumpIRInstrumentation DumpIR;
   PrintPassInstrumentation PrintPass;
   TimePassesHandler TimePasses;
   TimeProfilingPassesHandler TimeProfilingPasses;
diff --git a/llvm/lib/IR/PrintPasses.cpp b/llvm/lib/IR/PrintPasses.cpp
index e2ef20bb81ba7d..bec315cb90e7d6 100644
--- a/llvm/lib/IR/PrintPasses.cpp
+++ b/llvm/lib/IR/PrintPasses.cpp
@@ -33,6 +33,31 @@ static cl::opt<bool> PrintAfterAll("print-after-all",
                                    llvm::cl::desc("Print IR after each pass"),
                                    cl::init(false), cl::Hidden);
 
+static cl::list<std::string>
+    DumpBefore("dump-before",
+               llvm::cl::desc("Dump IR to a file before specified passes"),
+               cl::CommaSeparated, cl::Hidden);
+
+static cl::list<std::string>
+    DumpAfter("dump-after",
+              llvm::cl::desc("Dump IR to a file after specified passes"),
+              cl::CommaSeparated, cl::Hidden);
+
+static cl::opt<bool>
+    DumpBeforeAll("dump-before-all",
+                  llvm::cl::desc("Dump IR to a file before each pass"),
+                  cl::init(false), cl::Hidden);
+static cl::opt<bool>
+    DumpAfterAll("dump-after-all",
+                 llvm::cl::desc("Dump IR to a file after each pass"),
+                 cl::init(false), cl::Hidden);
+
+static cl::opt<std::string> DumpDirectory(
+    "ir-dump-directory",
+    llvm::cl::desc("A directory to dump IR log files into before and after "
+                   "passes as specified using -dump-before / -dump-after"),
+    cl::init(""), cl::Hidden, cl::value_desc("filename"));
+
 // Print out the IR after passes, similar to -print-after-all except that it
 // only prints the IR after passes that change the IR. Those passes that do not
 // make changes to the IR are reported as not making any changes. In addition,
@@ -139,6 +164,35 @@ std::vector<std::string> llvm::printAfterPasses() {
   return std::vector<std::string>(PrintAfter);
 }
 
+bool llvm::shouldDumpBeforeSomePass() {
+  return DumpBeforeAll || !DumpBefore.empty();
+}
+
+bool llvm::shouldDumpAfterSomePass() {
+  return DumpAfterAll || !DumpAfter.empty();
+}
+
+bool llvm::shouldDumpBeforeAll() { return DumpBeforeAll; }
+
+bool llvm::shouldDumpAfterAll() { return DumpAfterAll; }
+
+bool llvm::shouldDumpBeforePass(StringRef PassID) {
+  return DumpBeforeAll || llvm::is_contained(DumpBefore, PassID);
+}
+
+bool llvm::shouldDumpAfterPass(StringRef PassID) {
+  return DumpAfterAll || llvm::is_contained(DumpAfter, PassID);
+}
+std::vector<std::string> llvm::dumpBeforePasses() {
+  return std::vector<std::string>(DumpBefore);
+}
+
+std::vector<std::string> llvm::dumpAfterPasses() {
+  return std::vector<std::string>(DumpAfter);
+}
+
+StringRef llvm::irInstrumentationDumpDirectory() { return DumpDirectory; }
+
 bool llvm::forcePrintModuleIR() { return PrintModuleScope; }
 
 bool llvm::isPassInPrintList(StringRef PassName) {
diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp
index 1fdeb13706ca6a..c402399779683c 100644
--- a/llvm/lib/Passes/PassBuilder.cpp
+++ b/llvm/lib/Passes/PassBuilder.cpp
@@ -386,7 +386,8 @@ AnalysisKey NoOpLoopAnalysis::Key;
 /// We currently only use this for --print-before/after.
 bool shouldPopulateClassToPassNames() {
   return PrintPipelinePasses || !printBeforePasses().empty() ||
-         !printAfterPasses().empty() || !isFilterPassesEmpty();
+         !printAfterPasses().empty() || !isFilterPassesEmpty() ||
+         !dumpAfterPasses().empty() || !dumpBeforePasses().empty();
 }
 
 // A pass for testing -print-on-crash.
diff --git a/llvm/lib/Passes/StandardInstrumentations.cpp b/llvm/lib/Passes/StandardInstrumentations.cpp
index ce14489ba43474..48e85b97dbbb28 100644
--- a/llvm/lib/Passes/StandardInstrumentations.cpp
+++ b/llvm/lib/Passes/StandardInstrumentations.cpp
@@ -33,6 +33,7 @@
 #include "llvm/Support/FormatVariadic.h"
 #include "llvm/Support/GraphWriter.h"
 #include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Path.h"
 #include "llvm/Support/Program.h"
 #include "llvm/Support/Regex.h"
 #include "llvm/Support/Signals.h"
@@ -830,6 +831,182 @@ void PrintIRInstrumentation::registerCallbacks(
   }
 }
 
+void DumpIRInstrumentation::registerCallbacks(
+    PassInstrumentationCallbacks &PIC) {
+
+  if (!(shouldDumpBeforeSomePass() || shouldDumpAfterSomePass()))
+    return;
+
+  this->PIC = &PIC;
+
+  PIC.registerBeforeNonSkippedPassCallback(
+      [this](StringRef P, Any IR) { this->pushPass(P, IR); });
+
+  if (shouldDumpBeforeSomePass())
+    PIC.registerBeforeNonSkippedPassCallback(
+        [this](StringRef P, Any IR) { this->dumpBeforePass(P, IR); });
+
+  if (shouldDumpAfterSomePass()) {
+    PIC.registerAfterPassCallback(
+        [this](StringRef P, Any IR, const PreservedAnalyses &) {
+          this->dumpAfterPass(P, IR);
+        });
+  }
+
+  // It is important the the "popPass" callback fires after the dumpAfterPass
+  // callback
+  PIC.registerAfterPassCallback(
+      [this](StringRef P, Any IR, const PreservedAnalyses &) {
+        this->popPass(P);
+      });
+}
+
+void DumpIRInstrumentation::dumpBeforePass(StringRef PassID, Any IR) {
+  if (isIgnored(PassID))
+    return;
+
+  if (!shouldDumpBeforePass(PassID)) {
+    return;
+  }
+
+  SmallString<16> OutputPath =
+      fetchCurrentInstrumentationDumpFile("-before.ll");
+  std::error_code EC;
+  llvm::raw_fd_ostream OS(OutputPath, EC, llvm::sys::fs::CD_CreateAlways);
+  if (EC)
+    report_fatal_error(Twine("Failed to open ") + OutputPath +
+                       " to support dump-before: " + EC.message());
+
+  OS << "*** IR Dump Before " << PassID << " on " << getIRName(IR) << " ***\n";
+  unwrapAndPrint(OS, IR);
+}
+
+void DumpIRInstrumentation::dumpAfterPass(StringRef PassID, Any IR) {
+  if (isIgnored(PassID))
+    return;
+
+  if (!shouldDumpAfterPass(PassID))
+    return;
+
+  SmallString<16> OutputPath = fetchCurrentInstrumentationDumpFile("-after.ll");
+  std::error_code EC;
+  llvm::raw_fd_ostream OS(OutputPath, EC, llvm::sys::fs::CD_CreateAlways);
+  if (EC)
+    report_fatal_error(Twine("Failed to open ") + OutputPath +
+                       " to support -dump-after: " + EC.message());
+
+  OS << "*** IR Dump After " << PassID << " on " << getIRName(IR) << " ***\n";
+  unwrapAndPrint(OS, IR);
+}
+
+bool DumpIRInstrumentation::shouldDumpBeforePass(StringRef PassID) {
+  if (shouldDumpBeforeAll())
+    return true;
+
+  StringRef PassName = PIC->getPassNameForClassName(PassID);
+  return is_contained(dumpBeforePasses(), PassName);
+}
+
+bool DumpIRInstrumentation::shouldDumpAfterPass(StringRef PassID) {
+  if (shouldDumpAfterAll())
+    return true;
+
+  StringRef PassName = PIC->getPassNameForClassName(PassID);
+  return is_contained(dumpAfterPasses(), PassName);
+}
+
+void DumpIRInstrumentation::pushPass(StringRef PassID, Any IR) {
+  const Module *M = unwrapModule(IR);
+  if (CurrentModule != M) {
+    // If currentModule is nullptr, or is not equal to M, we are starting to
+    // process a new module.
+
+    // The first frame of the stack should maintain a frequency table
+    // for module level passes.
+    PipelineStateStack.clear();
+    PipelineStateStack.push_back(PipelineStateStackFrame(M->getName()));
+
+    CurrentModule = M;
+  }
+
+  PassRunsFrequencyTableT &FreqTable = PipelineStateStack.back().FreqTable;
+  if (FreqTable.find(PassID) == FreqTable.end())
+    FreqTable[PassID] = 0;
+
+  PipelineStateStack.push_back(PipelineStateStackFrame(PassID));
+}
+
+void DumpIRInstrumentation::popPass(StringRef PassID) {
+  PipelineStateStack.pop_back();
+  assert(!PipelineStateStack.empty());
+
+  PassRunsFrequencyTableT &FreqTable = PipelineStateStack.back().FreqTable;
+  assert(FreqTable.find(PassID) != FreqTable.end());
+  FreqTable[PassID]++;
+  PipelineStateStack.back().PassCount++;
+}
+
+StringRef DumpIRInstrumentation::fetchInstrumentationDumpDirectory() {
+  if (!InstrumentationDumpDirectory.empty())
+    return InstrumentationDumpDirectory;
+
+  if (!irInstrumentationDumpDirectory().empty())
+    return irInstrumentationDumpDirectory();
+
+  std::error_code EC =
+      sys::fs::createUniqueDirectory("dumped-ir", InstrumentationDumpDirectory);
+  if (EC)
+    report_fatal_error(
+        Twine("Failed to create unique directory for IR dumping: ") +
+        EC.message());
+
+  return InstrumentationDumpDirectory;
+}
+
+SmallString<16>
+DumpIRInstrumentation::fetchCurrentInstrumentationDumpFile(StringRef Suffix) {
+  SmallString<16> OutputPath;
+  sys::path::append(OutputPath, fetchInstrumentationDumpDirectory());
+  assert(CurrentModule);
+  sys::path::append(OutputPath, CurrentModule->getName());
+  auto *StateStackIt = PipelineStateStack.begin();
+  // Skip over the first frame in the stack which represents the module being
+  // processed
+  for (++StateStackIt; StateStackIt != PipelineStateStack.end();
+       ++StateStackIt) {
+    SmallString<8> PathComponentName;
+    // Check the previous frame's pass count to see how many passes of
+    // any kind have run on this "nesting level".
+    unsigned int PassCount = (StateStackIt - 1)->PassCount;
+    PathComponentName += std::to_string(PassCount);
+    PathComponentName += ".";
+    // Check the previous frame's frequency table to see how many times
+    // this pass has run at this "nesting level".
+    PassRunsFrequencyTableT &FreqTable = (StateStackIt - 1)->FreqTable;
+    StringRef FramePassID = StateStackIt->PassID;
+    PathComponentName += FramePassID;
+    PathComponentName += ".";
+    assert(FreqTable.find(FramePassID) != FreqTable.end() &&
+           "DumpIRInstrumentation pass frequency table missing entry");
+    // Make sure the uint is converted to a character and not interpretted as
+    // one
+    PathComponentName += std::to_string(FreqTable[FramePassID]);
+    sys::path::append(OutputPath, PathComponentName);
+  }
+  OutputPath += Suffix;
+
+  // Make sure the directory we wish to write our log file into exists.
+  StringRef ParentDirectory = sys::path::parent_path(OutputPath);
+  if (!ParentDirectory.empty()) {
+    if (auto EC = llvm::sys::fs::create_directories(ParentDirectory)) {
+      report_fatal_error(Twine("Failed to create directory '") +
+                         ParentDirectory + "' :" + EC.message());
+    }
+  }
+
+  return OutputPath;
+}
+
 void OptNoneInstrumentation::registerCallbacks(
     PassInstrumentationCallbacks &PIC) {
   PIC.registerShouldRunOptionalPassCallback(
@@ -2288,6 +2465,7 @@ void PrintCrashIRInstrumentation::registerCallbacks(
 void StandardInstrumentations::registerCallbacks(
     PassInstrumentationCallbacks &PIC, ModuleAnalysisManager *MAM) {
   PrintIR.registerCallbacks(PIC);
+  DumpIR.registerCallbacks(PIC);
   PrintPass.registerCallbacks(PIC);
   TimePasses.registerCallbacks(PIC);
   OptNone.registerCallbacks(PIC);
diff --git a/llvm/test/Other/dump-before-after-file-contents b/llvm/test/Other/dump-before-after-file-contents
new file mode 100644
index 00000000000000..6181ab90371e71
--- /dev/null
+++ b/llvm/test/Other/dump-before-after-file-contents
@@ -0,0 +1,76 @@
+; This first run is intended to test that the -dump* write an appropriate amount of IR to the file for different types of passes
+
+; RUN: opt %s -disable-output -passes='no-op-module,function(no-op-function),function(loop(no-op-loop)),no-op-module' -ir-dump-directory %t/logs -dump-after=no-op-module,no-op-function,no-op-loop -dump-before=no-op-module,no-op-function,no-op-loop
+
+; RUN: cat '%t/logs/%s/0.NoOpModulePass.0-before.ll' | FileCheck %s --check-prefix=MODULE-PASS --check-prefix=MODULE-PASS-BEFORE -DSOURCE_FILE_NAME=%s
+; RUN: cat '%t/logs/%s/0.NoOpModulePass.0-after.ll' | FileCheck %s --check-prefix=MODULE-PASS --check-prefix=MODULE-PASS-AFTER -DSOURCE_FILE_NAME=%s
+
+; MODULE-PASS-BEFORE: *** IR Dump Before NoOpModulePass on [module] ***
+; MODULE-PASS-AFTER: *** IR Dump After NoOpModulePass on [module] ***
+; MODULE-PASS: ; ModuleID = '[[SOURCE_FILE_NAME]]'
+; MODULE-PASS: source_filename = "[[SOURCE_FILE_NAME]]"
+; MODULE-PASS: define void @foo() {
+; MODULE-PASS:   ret void
+; MODULE-PASS: }
+; MODULE-PASS: define void @bar() {
+; MODULE-PASS: entry:
+; MODULE-PASS:   br label %loop
+; MODULE-PASS: loop:
+; MODULE-PASS:   br label %loop
+; MODULE-PASS: }
+
+; RUN: cat '%t/logs/%s/1.ModuleToFunctionPassAdaptor.0/0.PassManager<llvm::Function>.0/0.NoOpFunctionPass.0-before.ll' | FileCheck %s --check-prefix=FUNCTION-PASS-FOO --check-prefix=FUNCTION-PASS-FOO-BEFORE
+; RUN: cat '%t/logs/%s/1.ModuleToFunctionPassAdaptor.0/0.PassManager<llvm::Function>.0/0.NoOpFunctionPass.0-after.ll' | FileCheck %s --check-prefix=FUNCTION-PASS-FOO --check-prefix=FUNCTION-PASS-FOO-AFTER
+
+; FUNCTION-PASS-FOO-BEFORE: *** IR Dump Before NoOpFunctionPass on foo ***
+; FUNCTION-PASS-FOO-AFTER: *** IR Dump After NoOpFunctionPass on foo ***
+; FUNCTION-PASS-FOO: define void @foo() {
+; FUNCTION-PASS-FOO:   ret void
+; FUNCTION-PASS-FOO: }
+
+; RUN: cat '%t/logs/%s/1.ModuleToFunctionPassAdaptor.0/1.PassManager<llvm::Function>.1/0.NoOpFunctionPass.0-before.ll' | FileCheck %s --check-prefix=FUNCTION-PASS-BAR --check-prefix=FUNCTION-PASS-BAR-BEFORE
+; RUN: cat '%t/logs/%s/1.ModuleToFunctionPassAdaptor.0/1.PassManager<llvm::Function>.1/0.NoOpFunctionPass.0-after.ll' | FileCheck %s --check-prefix=FUNCTION-PASS-BAR --check-prefix=FUNCTION-PASS-BAR-AFTER
+
+; FUNCTION-PASS-BAR-BEFORE: *** IR Dump Before NoOpFunctionPass on bar ***
+; FUNCTION-PASS-BAR-AFTER:  *** IR Dump After NoOpFunctionPass on bar ***
+; FUNCTION-PASS-BAR: define void @bar() {
+; FUNCTION-PASS-BAR: entry:
+; FUNCTION-PASS-BAR:     br label %loop
+; FUNCTION-PASS-BAR: loop:
+; FUNCTION-PASS-BAR:     br label %loop
+; FUNCTION-PASS-BAR: }
+
+; RUN: cat '%t/logs/%s/2.ModuleToFunctionPassAdaptor.1/1.PassManager<llvm::Function>.1/0.FunctionToLoopPassAdaptor.0/1.PassManager<llvm::Loop, llvm::AnalysisManager<llvm::Loop, llvm::LoopStandardAnalysisResults&>, llvm::LoopStandardAnalysisResults&, llvm::LPMUpdater&>.0/0.NoOpLoopPass.0-before.ll' | FileCheck %s --check-prefix=LOOP-PASS --check-prefix=LOOP-PASS-BEFORE
+; RUN: cat '%t/logs/%s/2.ModuleToFunctionPassAdaptor.1/1.PassManager<llvm::Function>.1/0.FunctionToLoopPassAdaptor.0/1.PassManager<llvm::Loop, llvm::AnalysisManager<llvm::Loop, llvm::LoopStandardAnalysisResults&>, llvm::LoopStandardAnalysisResults&, llvm::LPMUpdater&>.0/0.NoOpLoopPass.0-after.ll' | FileCheck %s --check-prefix=LOOP-PASS --check-prefix=LOOP-PASS-AFTER
+
+; LOOP-PASS-BEFORE: *** IR Dump Before NoOpLoopPass on loop ***
+; LOOP-PASS-AFTER:  *** IR Dump After NoOpLoopPass on loop ***
+; LOOP-PASS: entry:
+; LOOP-PASS:   br label %loop
+; LOOP-PASS: ; Loop:
+; LOOP-PASS: loop:
+; LOOP-PASS:   br label %loop
+
+; This test verifies that dumping before and after really does dump before and after
+
+; RUN: rm -rf %t/logs
+; RUN: opt -S %s -passes=globaldce -ir-dump-directory %t/logs -dump-before=globaldce -dump-after=globaldce > %t/output.ll
+; Just compare the last few lines of all files to remove comments and header inserted by the logging
+; RUN: tail -n 11 %s > %t/normalized-input.ll
+; RUN: tail -n 11 %t/output.ll > %t/normalized-output.ll
+; RUN: tail -n 11 '%t/logs/%s/0.GlobalDCEPass.0-before.ll' > %t/normalized-before.ll
+; RUN: tail -n 11 '%t/logs/%s/0.GlobalDCEPass.0-after.ll' > %t/normalized-after.ll
+; RUN: diff %t/normalized-input.ll %t/normalized-before.ll
+; RUN: diff %t/normalized-output.ll %t/normalized-after.ll
+
+define void @foo() {
+  ret void
+}
+
+define void @bar() {
+entry:
+  br label %loop
+
+loop:                                             ; preds = %loop, %entry
+  br label %loop
+}
diff --git a/llvm/test/Other/dump-before-after-filenames b/llvm/test/Other/dump-before-after-filenames
new file mode 100644
index 00000000000000..0236baba733751
--- /dev/null
+++ b/llvm/test/Other/dump-before-after-filenames
@@ -0,0 +1,71 @@
+; RUN: mkdir -p %t/logs
+; RUN: rm -rf %t/logs
+
+; Basic dump before and after a single module pass
+; RUN: opt %s -disable-output -passes='no-op-module' -ir-dump-directory %t/logs -dump-after=no-op-module -dump-before=no-op-module
+; RUN: find %t/logs -type f -print | FileCheck %s --check-prefix=SINGLE-RUN -DSOURCE_FILE_NAME=%s
+; SINGLE-RUN-DAG: [[SOURCE_FILE_NAME]]/0.NoOpModulePass.0-before.ll
+; SINGLE-RUN-DAG: [[SOURCE_FILE_NAME]]/0.NoOpModulePass.0-after.ll
+; RUN: rm -rf %t/logs
+
+; Dump before and after multiple runs of the same module pass
+; RUN: opt %s -disable-output -passes='no-op-module,no-op-module,no-op-module' -ir-dump-directory %t/logs -dump-after=no-op-module -dump-before=no-op-module
+; RUN: find %t/logs -type f -print | FileCheck %s --check-prefix=MULTIPLE-RUNS -DSOURCE_FILE_NAME=%s
+; MULTIPLE-RUNS-DAG: [[SOURCE_FILE_NAME]]/0.NoOpModulePass.0-before.ll
+; MULTIPLE-RUNS-DAG: [[SOURCE_FILE_NAME]]/0.NoOpModulePass.0-after.ll
+; MULTIPLE-RUNS-DAG: [[SOURCE_FILE_NAME]]/1.NoOpModulePass.1-before.ll
+; MULTIPLE-RUNS-DAG: [[SOURCE_FILE_NAME]]/1.NoOpModulePass.1-after.ll
+; MULTIPLE-RUNS-DAG: [[SOURCE_FILE_NAME]]/2.NoOpModulePass.2-before.ll
+; MULTIPLE-RUNS-DAG: [[SOURCE_FILE_NAME]]/2.NoOpModulePass.2-after.ll
+; RUN: rm -rf %t/logs
+
+; Dump before and after multiple passes, some nested
+; RUN: opt %s -disable-output -passes='no-op-module,function(no-op-function),function(loop(no-op-loop)),no-op-module' -ir-dump-directory %t/logs -dump-after=no-op-module,no-op-function,no-op-loop -dump-before=no-op-module,no-op-function,no-op-loop
+; RUN: find %t/logs -type f -print | FileCheck %s --check-prefix=MULTIPLE-NESTED-RUNS -DSOURCE_FILE_NAME=%s
+; MULTIPLE-NESTED-RUNS-DAG: [[SOURCE_FILE_NAME]]/0.NoOpModulePass.0-before.ll
+; MULTIPLE-NESTED-RUNS-DAG: [[SOURCE_FILE_NAME]]/0.NoOpModulePass.0-after.ll
+; MULTIPLE-NESTED-RUNS-DAG: [[SOURCE_FILE_NAME]]/1.ModuleToFunctionPassAdaptor.0/0.PassManager<llvm::Function>.0/0.NoOpFunctionPass.0-before.ll
+; MULTIPLE-NESTED-RUNS-DAG: [[SOURCE_FILE_NAME]]/1.ModuleToFunctionPassAdaptor.0/0.PassManager<llvm::Function>.0/0.NoOpFunctionPass.0-after.ll
+; MULTIPLE-NESTED-RUNS-DAG: [[SOURCE_FILE_NAME]]/1.ModuleToFunctionPassAdaptor.0/1.PassManager<llvm::Function>.1/0.NoOpFunctionPass.0-before.ll
+; MULTIPLE-NESTED-RUNS-DAG: [[SOURCE_FILE_NAME]]/1.ModuleToFunctionPassAdaptor.0/1.PassManager<llvm::Function>.1/0.NoOpFunctionPass.0-after.ll
+; MULTIPLE-NESTED-RUNS-DAG: [[SOURCE_FILE_NAME]]/2.ModuleToFunctionPassAdaptor.1/1.PassManager<llvm::Function>.1/0.FunctionToLoopPassAdaptor.0/1.PassManager<llvm::Loop, llvm::AnalysisManager<llvm::Loop, llvm::LoopStandardAnalysisResults&>, llvm::LoopStandardAnalysisResults&, llvm::LPMUpdater&>.0/0.NoOpLoopPass.0-before.ll
+; MULTIPLE-NESTED-RUNS-DAG: [[SOURCE_FILE_NAME]]/2.ModuleToFunctionPassAdaptor.1/1.PassManager<llvm::Function>.1/0.FunctionToLoopPassAdaptor.0/1.PassManager<llvm::Loop, llvm::AnalysisManager<llvm::Loop, llvm::LoopStandardAnalysisResults&>, llvm::LoopStandardAnalysisResults&, llvm::LPMUpdater&>.0/0.NoOpLoopPass.0-after.ll
+; MULTIPLE-NESTED-RUNS-DAG: [[SOURCE_FILE_NAME]]/3.NoOpModulePass.1-before.ll
+; MULTIPLE-NESTED-RUNS-DAG: [[SOURCE_FILE_NAME]]/3.NoOpModulePass.1-after.ll
+; RUN: rm -rf %t/logs
+
+; Dump before and after all passes, some nested
+; RUN: opt %s -disable-output -passes='no-op-module,function(no-op-function),function(loop(no-op-loop)),no-op-module' -ir-dump-directory %t/logs -dump-after-all -dump-before-all
+; RUN: find %t/logs -type f -print | FileCheck %s --check-prefix=MULTIPLE-NESTED-RUNS-DUMP-ALL -DSOURCE_FILE_NAME=%s
+; MULTIPLE-NESTED-RUNS-DUMP-ALL-DAG: [[SOURCE_FILE_NAME]]/0.NoOpModulePass.0-before.ll
+; MULTIPLE-NESTED-RUNS-DUMP-ALL-DAG: [[SOURCE_FILE_NAME]]/0.NoOpModulePass.0-after.ll
+; MULTIPLE-NESTED-RUNS-DUMP-ALL-DAG: [[SOURCE_FILE_NAME]]/1.ModuleToFunctionPassAdaptor.0/0.PassManager<llvm::Function>.0/0.NoOpFunctionPass.0-before.ll
+; MULTIPLE-NESTED-RUNS-DUMP-ALL-DAG: [[SOURCE_FILE_NAME]]/1.ModuleToFunctionPassAdaptor.0/0.PassManager<llvm::Function>.0/0.NoOpFunctionPass.0-after.ll
+; MULTIPLE-NESTED-RUNS-DUMP-ALL-DAG: [[SOURCE_FILE_NAME]]/1.ModuleToFunctionPassAdaptor.0/1.PassManager<llvm::Function>.1/0.NoOpFunctionPass.0-before.ll
+; MULTIPLE-NESTED-RUNS-DUMP-ALL-DAG: [[SOURCE_FILE_NAME]]/1.ModuleToFunctionPassAdaptor.0/1.PassManager<llvm::Function>.1/0.NoOpFunctionPass.0-after.ll
+; MULTIPLE-NESTED-RUNS-DUMP-ALL-DAG: [[SOURCE_FILE_NAME]]/2.ModuleToFunctionPassAdaptor.1/0.PassManager<llvm::Function>.0/0.FunctionToLoopPassAdaptor.0/0.PassManager<llvm::Function>.0/0.LoopSimplifyPass.0-before.ll
+; MULTIPLE-NESTED-RUNS-DUMP-ALL-DAG: [[SOURCE_FILE_NAME]]/2.ModuleToFunctionPassAdaptor.1/0.PassManager<llvm::Function>.0/0.FunctionToLoopPassAdaptor.0/0.PassManager<llvm::Function>.0/0.LoopSimplifyPass.0-after.ll
+; MULTIPLE-NESTED-RUNS-DUMP-ALL-DAG: [[SOURCE_FILE_NAME]]/2.ModuleToFunctionPassAdaptor.1/0.PassManager<llvm::Function>.0/0.FunctionToLoopPassAdaptor.0/0.PassManager<llvm::Function>.0/1.LCSSAPass.0-before.ll
+; MULTIPLE-NESTED-RUNS-DUMP-ALL-DAG: [[SOURCE_FILE_NAME]]/2.ModuleToFunctionPassAdaptor.1/0.PassManager<llvm::Function>.0/0.FunctionToLoopPassAdaptor.0/0.PassManager<llvm::Function>.0/1.LCSSAPass.0-after.ll
+; MULTIPLE-NESTED-RUNS-DUMP-ALL-DAG: [[SOURCE_FILE_NAME]]/2.ModuleToFunctionPassAdaptor.1/1.PassManager<llvm::Function>.1/0.FunctionToLoopPassAdaptor.0/0.PassManager<llvm::Function>.0/0.LoopSimplifyPass.0-before.ll
+; MULTIPLE-NESTED-RUNS-DUMP-ALL-DAG: [[SOURCE_FILE_NAME]]/2.ModuleToFunctionPassAdaptor.1/1.PassManager<llvm::Function>.1/0.FunctionToLoopPassAdaptor.0/0.PassManager<llvm::Function>.0/0.LoopSimplifyPass.0-after.ll
+; MULTIPLE-NESTED-RUNS-DUMP-ALL-DAG: [[SOURCE_FILE_NAME]]/2.ModuleToFunctionPassAdaptor.1/1.PassManager<llvm::Function>.1/0.FunctionToLoopPassAdaptor.0/0.PassManager<llvm::Function>.0/1.LCSSAPass.0-before.ll
+; MULTIPLE-NESTED-RUNS-DUMP-ALL-DAG: [[SOURCE_FILE_NAME]]/2.ModuleToFunctionPassAdaptor.1/1.PassManager<llvm::Function>.1/0.FunctionToLoopPassAdaptor.0/0.PassManager<llvm::Function>.0/1.LCSSAPass.0-after.ll
+; MULTIPLE-NESTED-RUNS-DUMP-ALL-DAG: [[SOURCE_FILE_NAME]]/2.ModuleToFunctionPassAdaptor.1/1.PassManager<llvm::Function>.1/0.FunctionToLoopPassAdaptor.0/1.PassManager<llvm::Loop, llvm::AnalysisManager<llvm::Loop, llvm::LoopStandardAnalysisResults&>, llvm::LoopStandardAnalysisResults&, llvm::LPMUpdater&>.0/0.NoOpLoopPass.0-before.l
+; MULTIPLE-NESTED-RUNS-DUMP-ALL-DAG: [[SOURCE_FILE_NAME]]/2.ModuleToFunctionPassAdaptor.1/1.PassManager<llvm::Function>.1/0.FunctionToLoopPassAdaptor.0/1.PassManager<llvm::Loop, llvm::AnalysisManager<llvm::Loop, llvm::LoopStandardAnalysisResults&>, llvm::LoopStandardAnalysisResults&, llvm::LPMUpdater&>.0/0.NoOpLoopPass.0-after.ll
+; MULTIPLE-NESTED-RUNS-DUMP-ALL-DAG: [[SOURCE_FILE_NAME]]/3.NoOpModulePass.1-before.ll
+; MULTIPLE-NESTED-RUNS-DUMP-ALL-DAG: [[SOURCE_FILE_NAME]]/3.NoOpModulePass.1-after.ll
+; MULTIPLE-NESTED-RUNS-DUMP-ALL-DAG: [[SOURCE_FILE_NAME]]/4.VerifierPass.0-before.ll
+; MULTIPLE-NESTED-RUNS-DUMP-ALL-DAG: [[SOURCE_FILE_NAME]]/4.VerifierPass.0-after.ll
+; RUN: rm -rf %t/logs
+
+define void @foo() {
+    ret void
+}
+
+define void @bar() {
+entry:
+    br label %loop
+loop:
+    br label %loop
+}
diff --git a/llvm/test/Other/dump-before-after-multiple-modules b/llvm/test/Other/dump-before-after-multiple-modules
new file mode 100644
index 00000000000000..72aeddecf952ae
--- /dev/null
+++ b/llvm/test/Other/dump-before-after-multiple-modules
@@ -0,0 +1,36 @@
+; RUN: mkdir -p %t/logs
+; RUN: rm -rf %t/logs
+
+; RUN: split-file %s %t
+; RUN: opt %t/module-foo -disable-output -passes='no-op-module,function(no-op-function),function(loop(no-op-loop)),no-op-module' -ir-dump-directory %t/logs -dump-after=no-op-module,no-op-function,no-op-loop -dump-before=no-op-module,no-op-function,no-op-loop
+; RUN: opt %t/module-bar -disable-output -passes='no-op-module,function(no-op-function),function(loop(no-op-loop)),no-op-module' -ir-dump-directory %t/logs -dump-after=no-op-module,no-op-function,no-op-loop -dump-before=no-op-module,no-op-function,no-op-loop
+; RUN: find %t/logs -type f -print | FileCheck %s
+; CHECK-DAG: module-foo/0.NoOpModulePass.0-before.ll
+; CHECK-DAG: module-foo/0.NoOpModulePass.0-after.ll
+; CHECK-DAG: module-foo/1.ModuleToFunctionPassAdaptor.0/0.PassManager<llvm::Function>.0/0.NoOpFunctionPass.0-before.ll
+; CHECK-DAG: module-foo/1.ModuleToFunctionPassAdaptor.0/0.PassManager<llvm::Function>.0/0.NoOpFunctionPass.0-after.ll
+; CHECK-DAG: module-foo/3.NoOpModulePass.1-before.ll
+; CHECK-DAG: module-foo/3.NoOpModulePass.1-after.ll
+; CHECK-DAG: module-bar/0.NoOpModulePass.0-before.ll
+; CHECK-DAG: module-bar/0.NoOpModulePass.0-after.ll
+; CHECK-DAG: module-bar/1.ModuleToFunctionPassAdaptor.0/0.PassManager<llvm::Function>.0/0.NoOpFunctionPass.0-before.ll
+; CHECK-DAG: module-bar/1.ModuleToFunctionPassAdaptor.0/0.PassManager<llvm::Function>.0/0.NoOpFunctionPass.0-after.ll
+; CHECK-DAG: module-bar/2.ModuleToFunctionPassAdaptor.1/0.PassManager<llvm::Function>.0/0.FunctionToLoopPassAdaptor.0/1.PassManager<llvm::Loop, llvm::AnalysisManager<llvm::Loop, llvm::LoopStandardAnalysisResults&>, llvm::LoopStandardAnalysisResults&, llvm::LPMUpdater&>.0/0.NoOpLoopPass.0-before.ll
+; CHECK-DAG: module-bar/2.ModuleToFunctionPassAdaptor.1/0.PassManager<llvm::Function>.0/0.FunctionToLoopPassAdaptor.0/1.PassManager<llvm::Loop, llvm::AnalysisManager<llvm::Loop, llvm::LoopStandardAnalysisResults&>, llvm::LoopStandardAnalysisResults&, llvm::LPMUpdater&>.0/0.NoOpLoopPass.0-after.ll
+; CHECK-DAG: module-bar/3.NoOpModulePass.1-before.ll
+; CHECK-DAG: module-bar/3.NoOpModulePass.1-after.ll
+
+;--- module-foo
+
+define void @foo() {
+    ret void
+}
+
+;--- module-bar
+
+define void @bar() {
+entry:
+    br label %loop
+loop:
+    br label %loop
+}
diff --git a/llvm/test/Other/lit.local.cfg b/llvm/test/Other/lit.local.cfg
new file mode 100644
index 00000000000000..ae84c64a29c5ce
--- /dev/null
+++ b/llvm/test/Other/lit.local.cfg
@@ -0,0 +1 @@
+config.suffixes = ['', '.ll']



More information about the llvm-commits mailing list