[llvm] 4555304 - Reland "A new hidden option exec-on-ir-change=exe that calls exe each time IR changes"

Jamie Schmeiser via llvm-commits llvm-commits at lists.llvm.org
Mon Dec 5 11:11:37 PST 2022


Author: Jamie Schmeiser
Date: 2022-12-05T14:11:15-05:00
New Revision: 455530425e07b8f69bc96a52fa6a322fe022f25e

URL: https://github.com/llvm/llvm-project/commit/455530425e07b8f69bc96a52fa6a322fe022f25e
DIFF: https://github.com/llvm/llvm-project/commit/455530425e07b8f69bc96a52fa6a322fe022f25e.diff

LOG: Reland "A new hidden option exec-on-ir-change=exe that calls exe each time IR changes"

Summary:
This relands commit dff0e8b4ff13af311512c369d059f1e095e83a60.  The test is now
guarded with a lit.local.cfg that ensures /bin/cat is available.  Also, the
code has been updated to match changes made to relevant code.

Author: Jamie Schmeiser <schmeise at ca.ibm.com>
Reviewed By:aeubanks (Arthur Eubanks)
Differential Revision: https://reviews.llvm.org/D110776

Added: 
    llvm/test/Other/ChangeTesters/exec-on-ir-change.ll
    llvm/test/Other/ChangeTesters/lit.local.cfg

Modified: 
    llvm/include/llvm/IR/PrintPasses.h
    llvm/include/llvm/Passes/StandardInstrumentations.h
    llvm/lib/IR/PrintPasses.cpp
    llvm/lib/Passes/StandardInstrumentations.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/IR/PrintPasses.h b/llvm/include/llvm/IR/PrintPasses.h
index 71e8fb04fae02..95b97e76c867c 100644
--- a/llvm/include/llvm/IR/PrintPasses.h
+++ b/llvm/include/llvm/IR/PrintPasses.h
@@ -58,6 +58,18 @@ bool isFilterPassesEmpty();
 // Returns true if we should print the function.
 bool isFunctionInPrintList(StringRef FunctionName);
 
+// Ensure temporary files exist, creating or re-using them.  \p FD contains
+// file descriptors (-1 indicates that the file should be created) and
+// \p SR contains the corresponding initial content.  \p FileName will have
+// the filenames filled in when creating files.  Return first error code (if
+// any) and stop.
+std::error_code prepareTempFiles(SmallVector<int> &FD, ArrayRef<StringRef> SR,
+                                 SmallVector<std::string> &FileName);
+
+// Remove the temporary files in \p FileName.  Typically used in conjunction
+// with prepareTempFiles.  Return first error code (if any) and stop..
+std::error_code cleanUpTempFiles(ArrayRef<std::string> FileName);
+
 // Perform a system based 
diff  between \p Before and \p After, using \p
 // OldLineFormat, \p NewLineFormat, and \p UnchangedLineFormat to control the
 // formatting of the output. Return an error message for any failures instead

diff  --git a/llvm/include/llvm/Passes/StandardInstrumentations.h b/llvm/include/llvm/Passes/StandardInstrumentations.h
index 20211c18b3d97..c8614ec496883 100644
--- a/llvm/include/llvm/Passes/StandardInstrumentations.h
+++ b/llvm/include/llvm/Passes/StandardInstrumentations.h
@@ -262,6 +262,32 @@ class IRChangedPrinter : public TextChangeReporter<std::string> {
                    Any) override;
 };
 
+class IRChangedTester : public IRChangedPrinter {
+public:
+  IRChangedTester() : IRChangedPrinter(true) {}
+  ~IRChangedTester() override;
+  void registerCallbacks(PassInstrumentationCallbacks &PIC);
+
+protected:
+  void handleIR(const std::string &IR, StringRef PassID);
+
+  // Check initial IR
+  void handleInitialIR(Any IR) override;
+  // Do nothing.
+  void omitAfter(StringRef PassID, std::string &Name) override;
+  // Do nothing.
+  void handleInvalidated(StringRef PassID) override;
+  // Do nothing.
+  void handleFiltered(StringRef PassID, std::string &Name) override;
+  // Do nothing.
+  void handleIgnored(StringRef PassID, std::string &Name) override;
+
+  // Call test as interesting IR has changed.
+  void handleAfter(StringRef PassID, std::string &Name,
+                   const std::string &Before, const std::string &After,
+                   Any) override;
+};
+
 // Information that needs to be saved for a basic block in order to compare
 // before and after the pass to determine if it was changed by a pass.
 template <typename T> class BlockDataT {
@@ -536,6 +562,7 @@ class StandardInstrumentations {
   InLineChangePrinter PrintChangedDiff;
   DotCfgChangeReporter WebsiteChangeReporter;
   PrintCrashIRInstrumentation PrintCrashIR;
+  IRChangedTester ChangeTester;
   VerifyInstrumentation Verify;
 
   bool VerifyEach;

diff  --git a/llvm/lib/IR/PrintPasses.cpp b/llvm/lib/IR/PrintPasses.cpp
index ce5ce618b2e8e..d2a9827fd07c4 100644
--- a/llvm/lib/IR/PrintPasses.cpp
+++ b/llvm/lib/IR/PrintPasses.cpp
@@ -8,6 +8,7 @@
 
 #include "llvm/IR/PrintPasses.h"
 #include "llvm/Support/CommandLine.h"
+#include "llvm/Support/Errc.h"
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/Program.h"
@@ -155,37 +156,64 @@ bool llvm::isFunctionInPrintList(StringRef FunctionName) {
          PrintFuncNames.count(std::string(FunctionName));
 }
 
-std::string llvm::doSystemDiff(StringRef Before, StringRef After,
-                               StringRef OldLineFormat, StringRef NewLineFormat,
-                               StringRef UnchangedLineFormat) {
-  StringRef SR[2]{Before, After};
-  // Store the 2 bodies into temporary files and call 
diff  on them
-  // to get the body of the node.
-  const unsigned NumFiles = 3;
-  static std::string FileName[NumFiles];
-  static int FD[NumFiles]{-1, -1, -1};
-  for (unsigned I = 0; I < NumFiles; ++I) {
+std::error_code cleanUpTempFilesImpl(ArrayRef<std::string> FileName,
+                                     unsigned N) {
+  std::error_code RC;
+  for (unsigned I = 0; I < N; ++I) {
+    std::error_code EC = sys::fs::remove(FileName[I]);
+    if (EC)
+      RC = EC;
+  }
+  return RC;
+}
+
+std::error_code llvm::prepareTempFiles(SmallVector<int> &FD,
+                                       ArrayRef<StringRef> SR,
+                                       SmallVector<std::string> &FileName) {
+  assert(FD.size() >= SR.size() && FileName.size() == FD.size() &&
+         "Unexpected array sizes");
+  std::error_code EC;
+  unsigned I = 0;
+  for (; I < FD.size(); ++I) {
     if (FD[I] == -1) {
       SmallVector<char, 200> SV;
-      std::error_code EC =
-          sys::fs::createTemporaryFile("tmp
diff ", "txt", FD[I], SV);
+      EC = sys::fs::createTemporaryFile("tmpfile", "txt", FD[I], SV);
       if (EC)
-        return "Unable to create temporary file.";
+        break;
       FileName[I] = Twine(SV).str();
     }
-    // The third file is used as the result of the 
diff .
-    if (I == NumFiles - 1)
-      break;
+    if (I < SR.size()) {
+      EC = sys::fs::openFileForWrite(FileName[I], FD[I]);
+      if (EC)
+        break;
+      raw_fd_ostream OutStream(FD[I], /*shouldClose=*/true);
+      if (FD[I] == -1) {
+        EC = make_error_code(errc::io_error);
+        break;
+      }
+      OutStream << SR[I];
+    }
+  }
+  if (EC && I > 0)
+    // clean up created temporary files
+    cleanUpTempFilesImpl(FileName, I);
+  return EC;
+}
 
-    std::error_code EC = sys::fs::openFileForWrite(FileName[I], FD[I]);
-    if (EC)
-      return "Unable to open temporary file for writing.";
+std::error_code llvm::cleanUpTempFiles(ArrayRef<std::string> FileName) {
+  return cleanUpTempFilesImpl(FileName, FileName.size());
+}
 
-    raw_fd_ostream OutStream(FD[I], /*shouldClose=*/true);
-    if (FD[I] == -1)
-      return "Error opening file for writing.";
-    OutStream << SR[I];
-  }
+std::string llvm::doSystemDiff(StringRef Before, StringRef After,
+                               StringRef OldLineFormat, StringRef NewLineFormat,
+                               StringRef UnchangedLineFormat) {
+  // Store the 2 bodies into temporary files and call 
diff  on them
+  // to get the body of the node.
+  static SmallVector<int> FD{-1, -1, -1};
+  SmallVector<StringRef> SR{Before, After};
+  static SmallVector<std::string> FileName{"", "", ""};
+  if (auto Err = prepareTempFiles(FD, SR, FileName))
+    return "Unable to create temporary file.";
 
   static ErrorOr<std::string> DiffExe = sys::findProgramByName(DiffBinary);
   if (!DiffExe)
@@ -210,11 +238,8 @@ std::string llvm::doSystemDiff(StringRef Before, StringRef After,
   else
     return "Unable to read result.";
 
-  // Clean up.
-  for (const std::string &I : FileName) {
-    std::error_code EC = sys::fs::remove(I);
-    if (EC)
-      return "Unable to remove temporary file.";
-  }
+  if (auto Err = cleanUpTempFiles(FileName))
+    return "Unable to remove temporary file.";
+
   return Diff;
 }

diff  --git a/llvm/lib/Passes/StandardInstrumentations.cpp b/llvm/lib/Passes/StandardInstrumentations.cpp
index 1d58a8515118b..e1124a8ab88a6 100644
--- a/llvm/lib/Passes/StandardInstrumentations.cpp
+++ b/llvm/lib/Passes/StandardInstrumentations.cpp
@@ -109,6 +109,18 @@ static cl::opt<std::string> OptBisectPrintIRPath(
 
 namespace {
 
+// An option for specifying an executable that will be called with the IR
+// everytime it changes in the opt pipeline.  It will also be called on
+// the initial IR as it enters the pipeline.  The executable will be passed
+// the name of a temporary file containing the IR and the PassID.  This may
+// be used, for example, to call llc on the IR and run a test to determine
+// which pass makes a change that changes the functioning of the IR.
+// The usual modifier options work as expected.
+static cl::opt<std::string>
+    TestChanged("exec-on-ir-change", cl::Hidden, cl::init(""),
+                cl::desc("exe called with module IR after each pass that "
+                         "changes it"));
+
 /// Extract Module out of \p IR unit. May return nullptr if \p IR does not match
 /// certain global filters. Will never return nullptr if \p Force is true.
 const Module *unwrapModule(Any IR, bool Force = false) {
@@ -484,6 +496,57 @@ void IRChangedPrinter::handleAfter(StringRef PassID, std::string &Name,
   Out << "*** IR Dump After " << PassID << " on " << Name << " ***\n" << After;
 }
 
+IRChangedTester::~IRChangedTester() {}
+
+void IRChangedTester::registerCallbacks(PassInstrumentationCallbacks &PIC) {
+  if (TestChanged != "")
+    TextChangeReporter<std::string>::registerRequiredCallbacks(PIC);
+}
+
+void IRChangedTester::handleIR(const std::string &S, StringRef PassID) {
+  // Store the body into a temporary file
+  static SmallVector<int> FD{-1};
+  SmallVector<StringRef> SR{S};
+  static SmallVector<std::string> FileName{""};
+  if (auto Err = prepareTempFiles(FD, SR, FileName)) {
+    dbgs() << "Unable to create temporary file.";
+    return;
+  }
+  static ErrorOr<std::string> Exe = sys::findProgramByName(TestChanged);
+  if (!Exe) {
+    dbgs() << "Unable to find test-changed executable.";
+    return;
+  }
+
+  StringRef Args[] = {TestChanged, FileName[0], PassID};
+  int Result = sys::ExecuteAndWait(*Exe, Args);
+  if (Result < 0) {
+    dbgs() << "Error executing test-changed executable.";
+    return;
+  }
+
+  if (auto Err = cleanUpTempFiles(FileName))
+    dbgs() << "Unable to remove temporary file.";
+}
+
+void IRChangedTester::handleInitialIR(Any IR) {
+  // Always test the initial module.
+  // Unwrap and print directly to avoid filtering problems in general routines.
+  std::string S;
+  generateIRRepresentation(IR, "Initial IR", S);
+  handleIR(S, "Initial IR");
+}
+
+void IRChangedTester::omitAfter(StringRef PassID, std::string &Name) {}
+void IRChangedTester::handleInvalidated(StringRef PassID) {}
+void IRChangedTester::handleFiltered(StringRef PassID, std::string &Name) {}
+void IRChangedTester::handleIgnored(StringRef PassID, std::string &Name) {}
+void IRChangedTester::handleAfter(StringRef PassID, std::string &Name,
+                                  const std::string &Before,
+                                  const std::string &After, Any) {
+  handleIR(After, PassID);
+}
+
 template <typename T>
 void OrderedChangedData<T>::report(
     const OrderedChangedData &Before, const OrderedChangedData &After,
@@ -2119,6 +2182,9 @@ void StandardInstrumentations::registerCallbacks(
     Verify.registerCallbacks(PIC);
   PrintChangedDiff.registerCallbacks(PIC);
   WebsiteChangeReporter.registerCallbacks(PIC);
+
+  ChangeTester.registerCallbacks(PIC);
+
   PrintCrashIR.registerCallbacks(PIC);
   // TimeProfiling records the pass running time cost.
   // Its 'BeforePassCallback' can be appended at the tail of all the

diff  --git a/llvm/test/Other/ChangeTesters/exec-on-ir-change.ll b/llvm/test/Other/ChangeTesters/exec-on-ir-change.ll
new file mode 100644
index 0000000000000..c49296ecb3f47
--- /dev/null
+++ b/llvm/test/Other/ChangeTesters/exec-on-ir-change.ll
@@ -0,0 +1,103 @@
+; Simple checks of -exec-on-ir-change=cat functionality
+;
+; Simple functionality check.
+; RUN: opt -S -exec-on-ir-change=cat -passes=instsimplify 2>&1 -o /dev/null < %s | FileCheck %s --check-prefix=CHECK-SIMPLE
+;
+; Check that only the passes that change the IR are printed and that the
+; others (including g) are filtered out.
+; RUN: opt -S -exec-on-ir-change=cat -passes=instsimplify -filter-print-funcs=f  2>&1 -o /dev/null < %s | FileCheck %s --check-prefix=CHECK-FUNC-FILTER
+;
+; Check that the reporting of IRs respects -print-module-scope
+; RUN: opt -S -exec-on-ir-change=cat -passes=instsimplify -print-module-scope 2>&1 -o /dev/null < %s | FileCheck %s --check-prefix=CHECK-PRINT-MOD-SCOPE
+;
+; Check that the reporting of IRs respects -print-module-scope
+; RUN: opt -S -exec-on-ir-change=cat -passes=instsimplify -filter-print-funcs=f -print-module-scope 2>&1 -o /dev/null < %s | FileCheck %s --check-prefix=CHECK-FUNC-FILTER-MOD-SCOPE
+;
+; Check that reporting of multiple functions happens
+; RUN: opt -S -exec-on-ir-change=cat -passes=instsimplify -filter-print-funcs="f,g" 2>&1 -o /dev/null < %s | FileCheck %s --check-prefix=CHECK-FILTER-MULT-FUNC
+;
+; Check that the reporting of IRs respects -filter-passes
+; RUN: opt -S -exec-on-ir-change=cat -passes="instsimplify,no-op-function" -filter-passes="no-op-function" 2>&1 -o /dev/null < %s | FileCheck %s --check-prefix=CHECK-FILTER-PASSES
+;
+; Check that the reporting of IRs respects -filter-passes with multiple passes
+; RUN: opt -S -exec-on-ir-change=cat -passes="instsimplify,no-op-function" -filter-passes="no-op-function,instsimplify" 2>&1 -o /dev/null < %s | FileCheck %s --check-prefix=CHECK-FILTER-MULT-PASSES
+;
+; Check that the reporting of IRs respects both -filter-passes and -filter-print-funcs
+; RUN: opt -S -exec-on-ir-change=cat -passes="instsimplify,no-op-function" -filter-passes="no-op-function,instsimplify" -filter-print-funcs=f 2>&1 -o /dev/null < %s | FileCheck %s --check-prefix=CHECK-FILTER-FUNC-PASSES
+;
+; Check that the reporting of IRs respects -filter-passes, -filter-print-funcs and -print-module-scope
+; RUN: opt -S -exec-on-ir-change=cat -passes="instsimplify,no-op-function" -filter-passes="no-op-function,instsimplify" -filter-print-funcs=f -print-module-scope 2>&1 -o /dev/null < %s | FileCheck %s --check-prefix=CHECK-FILTER-FUNC-PASSES-MOD-SCOPE
+;
+; Check that repeated passes that change the IR are printed and that the
+; others (including g) are filtered out.  Note that the second time
+; instsimplify is run on f, it does not change the IR
+; RUN: opt -S -exec-on-ir-change=cat -passes="instsimplify,instsimplify" -filter-print-funcs=f  2>&1 -o /dev/null < %s | FileCheck %s --check-prefix=CHECK-MULT-PASSES-FILTER-FUNC
+;
+
+define i32 @g() {
+entry:
+  %a = add i32 2, 3
+  ret i32 %a
+}
+
+define i32 @f() {
+entry:
+  %a = add i32 2, 3
+  ret i32 %a
+}
+
+; CHECK-SIMPLE: ; ModuleID = {{.+}}
+; CHECK-SIMPLE: cat:{{.*}}Initial IR
+; CHECK-SIMPLE: define i32 @g()
+; CHECK-SIMPLE: cat:{{.*}}InstSimplifyPass
+; CHECK-SIMPLE: define i32 @f()
+; CHECK-SIMPLE: cat:{{.*}}InstSimplifyPass
+
+; CHECK-FUNC-FILTER: define i32 @f()
+; CHECK-FUNC-FILTER: cat:{{.*}}Initial IR
+; CHECK-FUNC-FILTER: define i32 @f()
+; CHECK-FUNC-FILTER: cat:{{.*}}InstSimplifyPass
+
+; CHECK-PRINT-MOD-SCOPE: ModuleID = {{.+}}
+; CHECK-PRINT-MOD-SCOPE: cat:{{.*}}Initial IR
+; CHECK-PRINT-MOD-SCOPE: ModuleID = {{.+}}
+; CHECK-PRINT-MOD-SCOPE: cat:{{.*}}InstSimplifyPass
+; CHECK-PRINT-MOD-SCOPE: ModuleID = {{.+}}
+; CHECK-PRINT-MOD-SCOPE: cat:{{.*}}InstSimplifyPass
+
+; CHECK-FUNC-FILTER-MOD-SCOPE: ; ModuleID = {{.+}}
+; CHECK-FUNC-FILTER-MOD-SCOPE: cat:{{.*}}Initial IR
+; CHECK-FUNC-FILTER-MOD-SCOPE: ModuleID = {{.+}}
+; CHECK-FUNC-FILTER-MOD-SCOPE: cat:{{.*}}InstSimplifyPass
+
+; CHECK-FILTER-MULT-FUNC: define i32 @g()
+; CHECK-FILTER-MULT-FUNC: cat:{{.*}}Initial IR
+; CHECK-FILTER-MULT-FUNC: define i32 @g()
+; CHECK-FILTER-MULT-FUNC: cat:{{.*}}InstSimplifyPass
+; CHECK-FILTER-MULT-FUNC: define i32 @f()
+; CHECK-FILTER-MULT-FUNC: cat:{{.*}}InstSimplifyPass
+
+; CHECK-FILTER-PASSES: define i32 @g()
+; CHECK-FILTER-PASSES: cat:{{.*}}Initial IR
+
+; CHECK-FILTER-MULT-PASSES: define i32 @g()
+; CHECK-FILTER-MULT-PASSES: cat:{{.*}}Initial IR
+; CHECK-FILTER-MULT-PASSES: define i32 @g()
+; CHECK-FILTER-MULT-PASSES: cat:{{.*}}InstSimplifyPass
+; CHECK-FILTER-MULT-PASSES: define i32 @f()
+; CHECK-FILTER-MULT-PASSES: cat:{{.*}}InstSimplifyPass
+
+; CHECK-FILTER-FUNC-PASSES: define i32 @f()
+; CHECK-FILTER-FUNC-PASSES: cat:{{.*}}Initial IR
+; CHECK-FILTER-FUNC-PASSES: define i32 @f()
+; CHECK-FILTER-FUNC-PASSES: cat:{{.*}}InstSimplifyPass
+
+; CHECK-FILTER-FUNC-PASSES-MOD-SCOPE: ; ModuleID = {{.+}}
+; CHECK-FILTER-FUNC-PASSES-MOD-SCOPE: cat:{{.*}}Initial IR
+; CHECK-FILTER-FUNC-PASSES-MOD-SCOPE: ModuleID = {{.+}}
+; CHECK-FILTER-FUNC-PASSES-MOD-SCOPE: cat:{{.*}}InstSimplifyPass
+
+; CHECK-MULT-PASSES-FILTER-FUNC: define i32 @f()
+; CHECK-MULT-PASSES-FILTER-FUNC: cat:{{.*}}Initial IR
+; CHECK-MULT-PASSES-FILTER-FUNC: define i32 @f()
+; CHECK-MULT-PASSES-FILTER-FUNC: cat:{{.*}}InstSimplifyPass

diff  --git a/llvm/test/Other/ChangeTesters/lit.local.cfg b/llvm/test/Other/ChangeTesters/lit.local.cfg
new file mode 100644
index 0000000000000..9925c80a7dc58
--- /dev/null
+++ b/llvm/test/Other/ChangeTesters/lit.local.cfg
@@ -0,0 +1,2 @@
+if not os.path.exists('/bin/cat'):
+  config.unsupported = True


        


More information about the llvm-commits mailing list