[llvm] [llvm] Improve IR dump to files (PR #165712)

via llvm-commits llvm-commits at lists.llvm.org
Thu Oct 30 06:37:10 PDT 2025


https://github.com/macurtis-amd updated https://github.com/llvm/llvm-project/pull/165712

>From c3f5555be20dc3655b400aa0b82de1e991499f00 Mon Sep 17 00:00:00 2001
From: Matthew Curtis <macurtis at amd.com>
Date: Thu, 30 Oct 2025 07:41:06 -0500
Subject: [PATCH] [llvm] Improve IR dump to files

---
 llvm/include/llvm/IR/PrintPasses.h            |  37 ++++
 .../llvm/Passes/StandardInstrumentations.h    |   9 +-
 llvm/lib/Analysis/CallGraphSCCPass.cpp        |  16 +-
 llvm/lib/Analysis/LoopPass.cpp                |   3 +-
 llvm/lib/Analysis/RegionPass.cpp              |   8 +-
 .../CodeGen/MachineFunctionPrinterPass.cpp    |   5 +-
 llvm/lib/IR/IRPrintingPasses.cpp              |  25 ++-
 llvm/lib/IR/LegacyPassManager.cpp             |  16 +-
 llvm/lib/IR/PrintPasses.cpp                   | 174 ++++++++++++++++++
 llvm/lib/Passes/StandardInstrumentations.cpp  |  33 +---
 llvm/test/Other/check-ir-dump-filenames.py    | 141 ++++++++++++++
 llvm/test/Other/dump-filenames.ll             |  85 +++++++++
 12 files changed, 485 insertions(+), 67 deletions(-)
 create mode 100644 llvm/test/Other/check-ir-dump-filenames.py
 create mode 100644 llvm/test/Other/dump-filenames.ll

diff --git a/llvm/include/llvm/IR/PrintPasses.h b/llvm/include/llvm/IR/PrintPasses.h
index 0aa1b379c35cf..0ec6f4c7b84d0 100644
--- a/llvm/include/llvm/IR/PrintPasses.h
+++ b/llvm/include/llvm/IR/PrintPasses.h
@@ -15,6 +15,9 @@
 
 namespace llvm {
 
+class raw_ostream;
+class raw_fd_ostream;
+
 enum class ChangePrinter {
   None,
   Verbose,
@@ -81,6 +84,40 @@ std::string doSystemDiff(StringRef Before, StringRef After,
                          StringRef OldLineFormat, StringRef NewLineFormat,
                          StringRef UnchangedLineFormat);
 
+// If not empty, print IR to files in this directory rather than written to
+// stderr.
+StringRef irDumpDirectory();
+inline bool shouldUseIRDumpDirectory() { return !irDumpDirectory().empty(); }
+
+// Generate the filename to use when dumping IR to files.
+enum class IRDumpFileSuffixType { Before, After, Invalidated };
+std::string irDumpFilename(StringRef Kind, StringRef PassName,
+                           std::optional<unsigned> PassNumber,
+                           IRDumpFileSuffixType SuffixType);
+
+// Helper class to manage dumping IR to files vs stderr.
+class IRDumpStream {
+  raw_fd_ostream *fstream;
+  raw_ostream &fallback;
+
+public:
+  // Build a banner appropriate for passing to the IRDumpStream constructor
+  // below.
+  //
+  // FIXME: Use structured data rather than a string.
+  static std::string buildBanner(llvm::StringRef PassName,
+                                 llvm::StringRef PassID,
+                                 IRDumpFileSuffixType SuffixType);
+
+  // If dumping to files, this will open a file with the appropriate
+  // pathname. 'Kind' and 'Banner' are used to generate the filename used.
+  IRDumpStream(StringRef Kind, StringRef Banner, raw_ostream &fallback);
+  ~IRDumpStream();
+
+  // If dumping to files, returns the file output stream, otherwise 'fallback'.
+  raw_ostream &os();
+};
+
 } // namespace llvm
 
 #endif // LLVM_IR_PRINTPASSES_H
diff --git a/llvm/include/llvm/Passes/StandardInstrumentations.h b/llvm/include/llvm/Passes/StandardInstrumentations.h
index 4ee5ab2554868..d524f32997867 100644
--- a/llvm/include/llvm/Passes/StandardInstrumentations.h
+++ b/llvm/include/llvm/Passes/StandardInstrumentations.h
@@ -25,6 +25,7 @@
 #include "llvm/IR/DroppedVariableStatsIR.h"
 #include "llvm/IR/OptBisect.h"
 #include "llvm/IR/PassTimingInfo.h"
+#include "llvm/IR/PrintPasses.h"
 #include "llvm/IR/ValueHandle.h"
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/Compiler.h"
@@ -81,14 +82,6 @@ class PrintIRInstrumentation {
   void pushPassRunDescriptor(StringRef PassID, Any IR, unsigned PassNumber);
   PassRunDescriptor popPassRunDescriptor(StringRef PassID);
 
-  enum class IRDumpFileSuffixType {
-    Before,
-    After,
-    Invalidated,
-  };
-
-  static StringRef
-  getFileSuffix(PrintIRInstrumentation::IRDumpFileSuffixType Type);
   std::string fetchDumpFilename(StringRef PassId, StringRef IRFileDisplayName,
                                 unsigned PassNumber,
                                 IRDumpFileSuffixType SuffixType);
diff --git a/llvm/lib/Analysis/CallGraphSCCPass.cpp b/llvm/lib/Analysis/CallGraphSCCPass.cpp
index de64fdb9548e6..4f9d3b6dfbb7e 100644
--- a/llvm/lib/Analysis/CallGraphSCCPass.cpp
+++ b/llvm/lib/Analysis/CallGraphSCCPass.cpp
@@ -675,18 +675,20 @@ namespace {
 
     bool runOnSCC(CallGraphSCC &SCC) override {
       bool BannerPrinted = false;
+
+      IRDumpStream dmp("call-graph-scc", Banner, OS);
       auto PrintBannerOnce = [&]() {
         if (BannerPrinted)
           return;
-        OS << Banner;
+        dmp.os() << Banner;
         BannerPrinted = true;
       };
 
       bool NeedModule = llvm::forcePrintModuleIR();
       if (isFunctionInPrintList("*") && NeedModule) {
         PrintBannerOnce();
-        OS << "\n";
-        SCC.getCallGraph().getModule().print(OS, nullptr);
+        dmp.os() << "\n";
+        SCC.getCallGraph().getModule().print(dmp.os(), nullptr);
         return false;
       }
       bool FoundFunction = false;
@@ -696,18 +698,18 @@ namespace {
             FoundFunction = true;
             if (!NeedModule) {
               PrintBannerOnce();
-              F->print(OS);
+              F->print(dmp.os());
             }
           }
         } else if (isFunctionInPrintList("*")) {
           PrintBannerOnce();
-          OS << "\nPrinting <null> Function\n";
+          dmp.os() << "\nPrinting <null> Function\n";
         }
       }
       if (NeedModule && FoundFunction) {
         PrintBannerOnce();
-        OS << "\n";
-        SCC.getCallGraph().getModule().print(OS, nullptr);
+        dmp.os() << "\n";
+        SCC.getCallGraph().getModule().print(dmp.os(), nullptr);
       }
       return false;
     }
diff --git a/llvm/lib/Analysis/LoopPass.cpp b/llvm/lib/Analysis/LoopPass.cpp
index d8680aac74b22..fcdc56a75ba77 100644
--- a/llvm/lib/Analysis/LoopPass.cpp
+++ b/llvm/lib/Analysis/LoopPass.cpp
@@ -51,7 +51,8 @@ class PrintLoopPassWrapper : public LoopPass {
     auto BBI = llvm::find_if(L->blocks(), [](BasicBlock *BB) { return BB; });
     if (BBI != L->blocks().end() &&
         isFunctionInPrintList((*BBI)->getParent()->getName())) {
-      printLoop(*L, OS, Banner);
+      IRDumpStream dmp("loop", Banner, OS);
+      printLoop(*L, dmp.os(), Banner);
     }
     return false;
   }
diff --git a/llvm/lib/Analysis/RegionPass.cpp b/llvm/lib/Analysis/RegionPass.cpp
index ae1d84659de86..66eab22d64570 100644
--- a/llvm/lib/Analysis/RegionPass.cpp
+++ b/llvm/lib/Analysis/RegionPass.cpp
@@ -191,12 +191,14 @@ class PrintRegionPass : public RegionPass {
   bool runOnRegion(Region *R, RGPassManager &RGM) override {
     if (!isFunctionInPrintList(R->getEntry()->getParent()->getName()))
       return false;
-    Out << Banner;
+
+    IRDumpStream dmp("region", Banner, Out);
+    dmp.os() << Banner;
     for (const auto *BB : R->blocks()) {
       if (BB)
-        BB->print(Out);
+        BB->print(dmp.os());
       else
-        Out << "Printing <null> Block";
+        dmp.os() << "Printing <null> Block";
     }
 
     return false;
diff --git a/llvm/lib/CodeGen/MachineFunctionPrinterPass.cpp b/llvm/lib/CodeGen/MachineFunctionPrinterPass.cpp
index 5111322023d04..33bddb7a150eb 100644
--- a/llvm/lib/CodeGen/MachineFunctionPrinterPass.cpp
+++ b/llvm/lib/CodeGen/MachineFunctionPrinterPass.cpp
@@ -46,9 +46,10 @@ struct MachineFunctionPrinterPass : public MachineFunctionPass {
   bool runOnMachineFunction(MachineFunction &MF) override {
     if (!isFunctionInPrintList(MF.getName()))
       return false;
-    OS << "# " << Banner << ":\n";
+    IRDumpStream dmp("machine-function", Banner, OS);
+    dmp.os() << "# " << Banner << ":\n";
     auto *SIWrapper = getAnalysisIfAvailable<SlotIndexesWrapperPass>();
-    MF.print(OS, SIWrapper ? &SIWrapper->getSI() : nullptr);
+    MF.print(dmp.os(), SIWrapper ? &SIWrapper->getSI() : nullptr);
     return false;
   }
 };
diff --git a/llvm/lib/IR/IRPrintingPasses.cpp b/llvm/lib/IR/IRPrintingPasses.cpp
index 5c062800198fc..0a48b67b8f0ef 100644
--- a/llvm/lib/IR/IRPrintingPasses.cpp
+++ b/llvm/lib/IR/IRPrintingPasses.cpp
@@ -12,6 +12,8 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/IR/IRPrintingPasses.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringExtras.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/IR/Function.h"
 #include "llvm/IR/Module.h"
@@ -20,6 +22,8 @@
 #include "llvm/Pass.h"
 #include "llvm/Support/Compiler.h"
 #include "llvm/Support/Debug.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Path.h"
 #include "llvm/Support/raw_ostream.h"
 
 using namespace llvm;
@@ -28,7 +32,7 @@ namespace {
 
 class PrintModulePassWrapper : public ModulePass {
   raw_ostream &OS;
-  std::string Banner;
+  const std::string Banner;
   bool ShouldPreserveUseListOrder;
 
 public:
@@ -44,19 +48,21 @@ class PrintModulePassWrapper : public ModulePass {
     // TODO: consider removing this as debug-intrinsics are gone.
     M.removeDebugIntrinsicDeclarations();
 
+    IRDumpStream dmp("module", Banner, OS);
+
     if (llvm::isFunctionInPrintList("*")) {
       if (!Banner.empty())
-        OS << Banner << "\n";
-      M.print(OS, nullptr, ShouldPreserveUseListOrder);
+        dmp.os() << Banner << "\n";
+      M.print(dmp.os(), nullptr, ShouldPreserveUseListOrder);
     } else {
       bool BannerPrinted = false;
       for (const auto &F : M.functions()) {
         if (llvm::isFunctionInPrintList(F.getName())) {
           if (!BannerPrinted && !Banner.empty()) {
-            OS << Banner << "\n";
+            dmp.os() << Banner << "\n";
             BannerPrinted = true;
           }
-          F.print(OS);
+          F.print(dmp.os());
         }
       }
     }
@@ -73,7 +79,7 @@ class PrintModulePassWrapper : public ModulePass {
 
 class PrintFunctionPassWrapper : public FunctionPass {
   raw_ostream &OS;
-  std::string Banner;
+  const std::string Banner;
 
 public:
   static char ID;
@@ -84,11 +90,12 @@ class PrintFunctionPassWrapper : public FunctionPass {
   // This pass just prints a banner followed by the function as it's processed.
   bool runOnFunction(Function &F) override {
     if (isFunctionInPrintList(F.getName())) {
+      IRDumpStream dmp("function", Banner, OS);
       if (forcePrintModuleIR())
-        OS << Banner << " (function: " << F.getName() << ")\n"
-           << *F.getParent();
+        dmp.os() << Banner << " (function: " << F.getName() << ")\n"
+                 << *F.getParent();
       else
-        OS << Banner << '\n' << static_cast<Value &>(F);
+        dmp.os() << Banner << '\n' << static_cast<Value &>(F);
     }
 
     return false;
diff --git a/llvm/lib/IR/LegacyPassManager.cpp b/llvm/lib/IR/LegacyPassManager.cpp
index 47a828842b481..282882b6caf89 100644
--- a/llvm/lib/IR/LegacyPassManager.cpp
+++ b/llvm/lib/IR/LegacyPassManager.cpp
@@ -736,10 +736,10 @@ void PMTopLevelManager::schedulePass(Pass *P) {
   }
 
   if (PI && !PI->isAnalysis() && shouldPrintBeforePass(PI->getPassArgument())) {
-    Pass *PP =
-        P->createPrinterPass(dbgs(), ("*** IR Dump Before " + P->getPassName() +
-                                      " (" + PI->getPassArgument() + ") ***")
-                                         .str());
+    Pass *PP = P->createPrinterPass(
+        dbgs(),
+        IRDumpStream::buildBanner(P->getPassName(), PI->getPassArgument(),
+                                  IRDumpFileSuffixType::Before));
     PP->assignPassManager(activeStack, getTopLevelPassManagerType());
   }
 
@@ -747,10 +747,10 @@ void PMTopLevelManager::schedulePass(Pass *P) {
   P->assignPassManager(activeStack, getTopLevelPassManagerType());
 
   if (PI && !PI->isAnalysis() && shouldPrintAfterPass(PI->getPassArgument())) {
-    Pass *PP =
-        P->createPrinterPass(dbgs(), ("*** IR Dump After " + P->getPassName() +
-                                      " (" + PI->getPassArgument() + ") ***")
-                                         .str());
+    Pass *PP = P->createPrinterPass(
+        dbgs(),
+        IRDumpStream::buildBanner(P->getPassName(), PI->getPassArgument(),
+                                  IRDumpFileSuffixType::After));
     PP->assignPassManager(activeStack, getTopLevelPassManagerType());
   }
 }
diff --git a/llvm/lib/IR/PrintPasses.cpp b/llvm/lib/IR/PrintPasses.cpp
index 610411a3cf978..d9b8b13e33fec 100644
--- a/llvm/lib/IR/PrintPasses.cpp
+++ b/llvm/lib/IR/PrintPasses.cpp
@@ -7,11 +7,16 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/IR/PrintPasses.h"
+#include "llvm/ADT/StringExtras.h"
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/Errc.h"
 #include "llvm/Support/FileSystem.h"
+#include "llvm/Support/FormatVariadic.h"
 #include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Path.h"
 #include "llvm/Support/Program.h"
+#include "llvm/Support/Threading.h"
+#include <atomic>
 #include <unordered_set>
 
 using namespace llvm;
@@ -33,6 +38,27 @@ static cl::opt<bool> PrintAfterAll("print-after-all",
                                    llvm::cl::desc("Print IR after each pass"),
                                    cl::init(false), cl::Hidden);
 
+static cl::opt<std::string> IRDumpDirectory(
+    "ir-dump-directory",
+    cl::desc("If specified, IR printed using the "
+             "-print-[before|after]{-all} options will be dumped into "
+             "files in this directory rather than written to stderr"),
+    cl::Hidden, cl::value_desc("filename"));
+
+StringRef llvm::irDumpDirectory() { return IRDumpDirectory; }
+
+static cl::opt<std::string> IRDumpFilenameFormat(
+    "ir-dump-filename-format",
+    cl::desc("Specifies how filenames are generated when dumping IR to files."
+             " Supported values are 'default' and 'sortable'."),
+    cl::Hidden, cl::init("default"));
+
+static cl::opt<bool> IRDumpFilenamePrependThreadId(
+    "ir-dump-filename-prepend-thread-id",
+    cl::desc("Prepend the filename with the current thread id. Usefule for"
+             " multi-threaded LTO compiles"),
+    cl::Hidden);
+
 // 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,
@@ -251,3 +277,151 @@ std::string llvm::doSystemDiff(StringRef Before, StringRef After,
 
   return Diff;
 }
+
+namespace {
+const char *getFileSuffix(IRDumpFileSuffixType Type) {
+  if (Type == IRDumpFileSuffixType::Before)
+    return "before";
+  if (Type == IRDumpFileSuffixType::After)
+    return "after";
+  if (Type == IRDumpFileSuffixType::Invalidated)
+    return "invalidated";
+  return "unknown";
+};
+} // namespace
+
+std::string llvm::irDumpFilename(StringRef Kind, StringRef PassNameIn,
+                                 std::optional<unsigned> PassNumber,
+                                 IRDumpFileSuffixType SuffixType) {
+
+  // sanitize PassName
+  std::string PassName = PassNameIn.str();
+  for (char &c : PassName) {
+    if (!isAlnum(c) && c != '-')
+      c = '_';
+  }
+
+  // One-time initialization of format string and generation of placeholders
+  static std::string Fmt;
+  static std::once_flag InitFmtFlag;
+  static auto InitFmt = []() {
+    static const char *FmtTidPrefix = "{6,0+8}-";
+    static const char *FmtDefault = "{1}-{2}-{3}-{5}.ll";
+    static const char *FmtSortable = "{0,0+8}-{1,0+8}-{2}-{3}-{4}-{5}.ll";
+    if (IRDumpFilenamePrependThreadId)
+      Fmt += FmtTidPrefix;
+    if (IRDumpFilenameFormat == "sortable")
+      Fmt += FmtSortable;
+    else if (IRDumpFilenameFormat == "default")
+      Fmt += FmtDefault;
+    else {
+      Fmt += FmtDefault;
+      errs() << "warning: invalid value for -ir-dump-filename-format '"
+             << IRDumpFilenameFormat << "'; using 'default'\n";
+    }
+  };
+  std::call_once(InitFmtFlag, InitFmt);
+
+  // Generate filename
+  static std::atomic<int> Ordinal;
+  int Ord = Ordinal++;
+  std::string Filename = formatv(false, Fmt.c_str(),
+                                 /* 0 */ Ord,
+                                 /* 1 */ PassNumber.value_or(Ord),
+                                 /* 2 */ Kind,
+                                 /* 3 */ PassName,
+                                 /* 4 */ static_cast<size_t>(SuffixType),
+                                 /* 5 */ getFileSuffix(SuffixType),
+                                 /* 6 */ llvm::get_threadid());
+
+  // Generate path
+  const StringRef DumpDir = irDumpDirectory();
+  assert(!DumpDir.empty() &&
+         "The flag -ir-dump-directory must be passed to dump IR to files");
+  SmallString<128> ResultPath;
+  sys::path::append(ResultPath, DumpDir, Filename);
+  return std::string(ResultPath);
+}
+
+std::string llvm::IRDumpStream::buildBanner(llvm::StringRef PassName,
+                                            llvm::StringRef PassID,
+                                            IRDumpFileSuffixType SuffixType) {
+
+  const char *Suffix = "Unknown";
+  if (SuffixType == IRDumpFileSuffixType::Before)
+    Suffix = "Before";
+  else if (SuffixType == IRDumpFileSuffixType::After)
+    Suffix = "After";
+  else if (SuffixType == IRDumpFileSuffixType::Invalidated)
+    Suffix = "Invalidated";
+
+  return formatv("*** IR Dump {0} {1} ({2}) ***", Suffix, PassName, PassID)
+      .str();
+}
+
+static std::string extractPassName(StringRef Banner,
+                                   IRDumpFileSuffixType &PhaseOut) {
+  StringRef Phase;
+  if (Banner.consume_front("*** IR Dump Before ")) {
+    PhaseOut = IRDumpFileSuffixType::Before;
+  } else if (Banner.consume_front("*** IR Dump After ")) {
+    PhaseOut = IRDumpFileSuffixType::After;
+  } else {
+    return std::string();
+  }
+
+  size_t open = Banner.find(" (");
+  if (open == StringRef::npos) {
+    return std::string();
+  }
+
+  open += 2;
+  size_t close = Banner.find(')', open);
+  if (close == StringRef::npos) {
+    return std::string();
+  }
+
+  return Banner.substr(open, close - open).str();
+}
+
+llvm::IRDumpStream::IRDumpStream(StringRef Kind, StringRef Banner,
+                                 raw_ostream &fallback)
+    : fstream(nullptr), fallback(fallback) {
+
+  IRDumpFileSuffixType Phase;
+  std::string PassName = extractPassName(Banner, Phase);
+
+  StringRef Dir = irDumpDirectory();
+  if (Dir.empty() || PassName.empty())
+    return;
+
+  std::string DumpIRFilename =
+      irDumpFilename(Kind, PassName, std::nullopt, Phase);
+
+  std::error_code EC = llvm::sys::fs::create_directories(Dir);
+  if (EC) {
+    report_fatal_error(Twine("Failed to create directory ") + Dir +
+                       " to support -ir-dump-directory: " + EC.message());
+  }
+  if (sys::fs::exists(DumpIRFilename)) {
+    errs() << "warning: overwriting existing file '" << DumpIRFilename << "'\n";
+  }
+  int FD = 0;
+  EC = sys::fs::openFile(DumpIRFilename, FD, sys::fs::CD_OpenAlways,
+                         sys::fs::FA_Write, sys::fs::OF_Text);
+  if (EC) {
+    report_fatal_error(Twine("Failed to open ") + DumpIRFilename +
+                       " to support -ir-dump-directory: " + EC.message());
+  }
+  // return FD;
+  fstream = new raw_fd_ostream(FD, /* shouldClose */ true);
+}
+
+llvm::IRDumpStream::~IRDumpStream() {
+  if (fstream) {
+    fstream->close();
+    delete fstream;
+  }
+}
+
+raw_ostream &llvm::IRDumpStream::os() { return fstream ? *fstream : fallback; }
diff --git a/llvm/lib/Passes/StandardInstrumentations.cpp b/llvm/lib/Passes/StandardInstrumentations.cpp
index 7290a86503120..940f8bb6461d2 100644
--- a/llvm/lib/Passes/StandardInstrumentations.cpp
+++ b/llvm/lib/Passes/StandardInstrumentations.cpp
@@ -128,13 +128,6 @@ static cl::list<unsigned> PrintAfterPassNumber(
     cl::desc("Print IR after the passes with specified numbers as "
              "reported by print-pass-numbers"));
 
-static cl::opt<std::string> IRDumpDirectory(
-    "ir-dump-directory",
-    cl::desc("If specified, IR printed using the "
-             "-print-[before|after]{-all} options will be dumped into "
-             "files in this directory rather than written to stderr"),
-    cl::Hidden, cl::value_desc("filename"));
-
 static cl::opt<bool>
     DroppedVarStats("dropped-variable-stats", cl::Hidden,
                     cl::desc("Dump dropped debug variables stats"),
@@ -788,28 +781,10 @@ static std::string getIRFileDisplayName(Any IR) {
   return Result;
 }
 
-StringRef PrintIRInstrumentation::getFileSuffix(IRDumpFileSuffixType Type) {
-  static constexpr std::array FileSuffixes = {"-before.ll", "-after.ll",
-                                              "-invalidated.ll"};
-  return FileSuffixes[static_cast<size_t>(Type)];
-}
-
 std::string PrintIRInstrumentation::fetchDumpFilename(
     StringRef PassName, StringRef IRFileDisplayName, unsigned PassNumber,
     IRDumpFileSuffixType SuffixType) {
-  assert(!IRDumpDirectory.empty() &&
-         "The flag -ir-dump-directory must be passed to dump IR to files");
-
-  SmallString<64> Filename;
-  raw_svector_ostream FilenameStream(Filename);
-  FilenameStream << PassNumber;
-  FilenameStream << '-' << IRFileDisplayName << '-';
-  FilenameStream << PassName;
-  FilenameStream << getFileSuffix(SuffixType);
-
-  SmallString<128> ResultPath;
-  sys::path::append(ResultPath, IRDumpDirectory, Filename);
-  return std::string(ResultPath);
+  return irDumpFilename(IRFileDisplayName, PassName, PassNumber, SuffixType);
 }
 
 void PrintIRInstrumentation::pushPassRunDescriptor(StringRef PassID, Any IR,
@@ -880,7 +855,7 @@ void PrintIRInstrumentation::printBeforePass(StringRef PassID, Any IR) {
     unwrapAndPrint(Stream, IR);
   };
 
-  if (!IRDumpDirectory.empty()) {
+  if (shouldUseIRDumpDirectory()) {
     std::string DumpIRFilename =
         fetchDumpFilename(PassID, getIRFileDisplayName(IR), CurrentPassNumber,
                           IRDumpFileSuffixType::Before);
@@ -915,7 +890,7 @@ void PrintIRInstrumentation::printAfterPass(StringRef PassID, Any IR) {
     unwrapAndPrint(Stream, IR);
   };
 
-  if (!IRDumpDirectory.empty()) {
+  if (shouldUseIRDumpDirectory()) {
     std::string DumpIRFilename =
         fetchDumpFilename(PassID, getIRFileDisplayName(IR), CurrentPassNumber,
                           IRDumpFileSuffixType::After);
@@ -953,7 +928,7 @@ void PrintIRInstrumentation::printAfterPassInvalidated(StringRef PassID) {
     printIR(Stream, M);
   };
 
-  if (!IRDumpDirectory.empty()) {
+  if (shouldUseIRDumpDirectory()) {
     std::string DumpIRFilename =
         fetchDumpFilename(PassID, IRFileDisplayName, PassNumber,
                           IRDumpFileSuffixType::Invalidated);
diff --git a/llvm/test/Other/check-ir-dump-filenames.py b/llvm/test/Other/check-ir-dump-filenames.py
new file mode 100644
index 0000000000000..132f3fe223750
--- /dev/null
+++ b/llvm/test/Other/check-ir-dump-filenames.py
@@ -0,0 +1,141 @@
+#!/usr/bin/python3
+
+import re
+import sys
+import os
+
+dir = sys.argv[1]
+pat = pat_arg = sys.argv[2:]
+pat = "".join(pat)
+pat = re.compile(pat)
+
+filenames = os.listdir(dir)
+if len(filenames) < 2:
+    print(f"expecting at least 2 files in {dir} but got {len(filenames)}")
+    sys.exit(1)
+
+
+def fndict(fn):
+    m = pat.match(fn)
+    if not m:
+        print(f"filename '{fn}' does not match pattern '{pat_arg}'")
+        sys.exit(1)
+    return m.groupdict()
+
+
+first = fndict(filenames[0])
+should_check_ordinal = "ordinal" in first
+should_check_suffix = "suffix" in first
+should_check_suffix_ordinal = should_check_suffix and "suffix_ordinal" in first
+should_check_before_after = should_check_suffix and should_check_ordinal
+
+should_check_pass_id = "pass_id" in first
+should_check_kind = "kind" in first
+should_check_pass_number = "pass_number" in first
+
+if should_check_ordinal:
+    filenames = sorted(filenames, key=lambda x: fndict(x)["ordinal"])
+first = fndict(filenames[0])
+second = fndict(filenames[1])
+
+
+def failed(msg):
+    print(f"error: {msg}")
+    sys.exit(1)
+
+
+def check(actual, expected, name):
+    if actual != expected:
+        failed(f"error: expected {name} '{expected}' but got '{actual}' ")
+
+
+# ------------------------------------------------------------------------------
+# ordinal
+# ------------------------------------------------------------------------------
+def check_ordinal(d, prev):
+    if not should_check_ordinal:
+        return
+    actual = int(d["ordinal"])
+    expected = int(prev["ordinal"]) + 1
+    check(actual, expected, "ordinal")
+
+
+# ------------------------------------------------------------------------------
+# suffix
+# ------------------------------------------------------------------------------
+suffix_ordinal_list = [ "before", "after" ]
+suffix_map = {}
+if should_check_before_after:
+    suffix_map |= { first["suffix"] : second["suffix"] }
+    suffix_map |= { second["suffix"] : first["suffix"] }
+
+
+def check_suffix(d, prev):
+    if not should_check_suffix:
+        return
+    suffix = d["suffix"]
+    if should_check_before_after:
+        if suffix not in suffix_map:
+            failed(f"suffix '{suffix}' not in {suffix_map}")
+        if prev is not None:
+            check(suffix, suffix_map[prev["suffix"]], "suffix")
+    else:
+        if suffix not in suffix_ordinal_list:
+            failed(f"suffix '{suffix}' not in {suffix_ordinal_list}")
+    if should_check_suffix_ordinal:
+        suffix_ordinal = int(d["suffix_ordinal"])
+        check(suffix_ordinal, suffix_ordinal_list.index(suffix),
+              "suffix ordinal")
+
+
+# ------------------------------------------------------------------------------
+# pass number
+# ------------------------------------------------------------------------------
+def check_pass_number(d, prev):
+    if not should_check_pass_number or not should_check_before_after:
+        return
+    actual = d["pass_number"]
+    if d["suffix"] == "after":
+        expected = prev["pass_number"]
+        check(actual, expected, "pass number")
+
+
+# ------------------------------------------------------------------------------
+# pass id
+# ------------------------------------------------------------------------------
+def check_pass_id(d, prev):
+    if not should_check_pass_id or not should_check_before_after:
+        return
+    actual = d["pass_id"]
+    if d["suffix"] == "after":
+        expected = prev["pass_id"]
+        check(actual, expected, "pass id")
+
+
+# ------------------------------------------------------------------------------
+# kind
+# ------------------------------------------------------------------------------
+def check_kind(d, prev):
+    if not should_check_kind or not should_check_before_after:
+        return
+    actual = d["kind"]
+    if d["suffix"] == "after":
+        expected = prev["kind"]
+        check(actual, expected, "kind")
+
+
+# ------------------------------------------------------------------------------
+#
+# ------------------------------------------------------------------------------
+check_ordinal(first, { "ordinal" : -1 })
+
+prev = first
+filenames = filenames[1:]
+for fn in filenames:
+    d = fndict(fn)
+    check_ordinal(d, prev)
+    check_suffix(d, prev)
+    check_pass_id(d, prev)
+    check_pass_number(d, prev)
+    check_kind(d, prev)
+    prev = d
diff --git a/llvm/test/Other/dump-filenames.ll b/llvm/test/Other/dump-filenames.ll
new file mode 100644
index 0000000000000..aa2170a4cf564
--- /dev/null
+++ b/llvm/test/Other/dump-filenames.ll
@@ -0,0 +1,85 @@
+; REQUIRES: default_triple
+
+;-------------------------------------------------------------------------------
+; default 
+;-------------------------------------------------------------------------------
+; RUN: rm -rf %t/logs
+; RUN: llc %s -o - -O3 -print-after-all -print-before-all \
+; RUN:     -ir-dump-directory %t/logs
+; RUN: %python %p/check-ir-dump-filenames.py %t/logs \
+; RUN:     "(?P<pass_number>[0-9]+)" \
+; RUN:     "-(?P<kind>module|function|machine-function)" \
+; RUN:     "-(?P<pass_id>.+)" \
+; RUN:     "-(?P<suffix>before|after)"
+
+; RUN: rm -rf %t/logs
+; RUN: opt %s -disable-output -O3 -print-after-all -print-before-all \
+; RUN:     -ir-dump-directory %t/logs
+; RUN: %python %p/check-ir-dump-filenames.py %t/logs \
+; RUN:     "(?P<pass_number>[0-9]+)" \
+; RUN:     "-(?P<kind>[0-9a-f]+-(module|((function|scc)-[0-9a-f]+)))" \
+; RUN:     "-(?P<pass_id>.+)" \
+; RUN:     "-(?P<suffix>before|after)"
+
+; RUN: rm -rf %t/logs
+; RUN: llc %s -o - -O3 -print-before-all \
+; RUN:     -ir-dump-directory %t/logs
+; RUN: %python %p/check-ir-dump-filenames.py %t/logs \
+; RUN:     "(?P<pass_number>[0-9]+)" \
+; RUN:     "-(?P<kind>module|function|machine-function)" \
+; RUN:     "-(?P<pass_id>.+)" \
+; RUN:     "-(?P<before>before)"
+
+; RUN: rm -rf %t/logs
+; RUN: opt %s -disable-output -O3 -print-after-all \
+; RUN:     -ir-dump-directory %t/logs
+; RUN: %python %p/check-ir-dump-filenames.py %t/logs \
+; RUN:     "(?P<pass_number>[0-9]+)" \
+; RUN:     "-(?P<kind>[0-9a-f]+-(module|((function|scc)-[0-9a-f]+)))" \
+; RUN:     "-(?P<pass_id>.+)" \
+; RUN:     "-(?P<after>after)"
+
+;-------------------------------------------------------------------------------
+; sortable
+;-------------------------------------------------------------------------------
+; RUN: rm -rf %t/logs
+; RUN: llc %s -o - -O3 -print-after-all -print-before-all \
+; RUN:     -ir-dump-directory %t/logs \
+; RUN:     -ir-dump-filename-format sortable
+; RUN: %python %p/check-ir-dump-filenames.py %t/logs \
+; RUN:     "(?P<ordinal>[0-9]{8})" \
+; RUN:     "-(?P<pass_ordinal>[0-9]{8})" \
+; RUN:     "-(?P<kind>module|function|machine-function)" \
+; RUN:     "-(?P<pass_id>.+)" \
+; RUN:     "-(?P<suffix_ordinal>[01])" \
+; RUN:     "-(?P<suffix>before|after)"
+
+; RUN: rm -rf %t/logs
+; RUN: opt %s -disable-output -O3 -print-after-all -print-before-all \
+; RUN:     -ir-dump-directory %t/logs \
+; RUN:     -ir-dump-filename-format sortable
+; RUN: %python %p/check-ir-dump-filenames.py %t/logs \
+; RUN:     "(?P<ordinal>[0-9]{8})" \
+; RUN:     "-(?P<pass_number>[0-9]+)" \
+; RUN:     "-(?P<kind>[0-9a-f]+-(module|((function|scc)-[0-9a-f]+)))" \
+; RUN:     "-(?P<pass_id>.+)" \
+; RUN:     "-(?P<suffix_ordinal>[01])" \
+; RUN:     "-(?P<suffix>before|after)"
+
+;-------------------------------------------------------------------------------
+; thread id
+;-------------------------------------------------------------------------------
+; RUN: rm -rf %t/logs
+; RUN: llc %s -o - -O3 -print-after-all \
+; RUN:     -ir-dump-directory %t/logs \
+; RUN:     -ir-dump-filename-prepend-thread-id
+; RUN: %python %p/check-ir-dump-filenames.py %t/logs \
+; RUN:     "(?P<tid>[0-9]+)" \
+; RUN:     "-(?P<pass_number>[0-9]+)" \
+; RUN:     "-(?P<kind>module|function|machine-function)" \
+; RUN:     "-(?P<pass_id>.+)" \
+; RUN:     "-(?P<after>after)"
+
+define void @foo() {
+    ret void
+}



More information about the llvm-commits mailing list