diff --git a/llvm/include/llvm/IR/PrintPasses.h b/llvm/include/llvm/IR/PrintPasses.h --- a/llvm/include/llvm/IR/PrintPasses.h +++ b/llvm/include/llvm/IR/PrintPasses.h @@ -58,6 +58,18 @@ // 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 &FD, ArrayRef SR, + SmallVector &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 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 --- a/llvm/include/llvm/Passes/StandardInstrumentations.h +++ b/llvm/include/llvm/Passes/StandardInstrumentations.h @@ -262,6 +262,32 @@ 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 class BlockDataT { @@ -536,6 +562,7 @@ 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 --- 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 @@ 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 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 &FD, + ArrayRef SR, + SmallVector &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 SV; - std::error_code EC = - sys::fs::createTemporaryFile("tmpdiff", "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 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 FD{-1, -1, -1}; + SmallVector SR{Before, After}; + static SmallVector FileName{"", "", ""}; + if (auto Err = prepareTempFiles(FD, SR, FileName)) + return "Unable to create temporary file."; static ErrorOr DiffExe = sys::findProgramByName(DiffBinary); if (!DiffExe) @@ -210,11 +238,8 @@ 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 --- a/llvm/lib/Passes/StandardInstrumentations.cpp +++ b/llvm/lib/Passes/StandardInstrumentations.cpp @@ -109,6 +109,18 @@ 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 + 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 @@ Out << "*** IR Dump After " << PassID << " on " << Name << " ***\n" << After; } +IRChangedTester::~IRChangedTester() {} + +void IRChangedTester::registerCallbacks(PassInstrumentationCallbacks &PIC) { + if (TestChanged != "") + TextChangeReporter::registerRequiredCallbacks(PIC); +} + +void IRChangedTester::handleIR(const std::string &S, StringRef PassID) { + // Store the body into a temporary file + static SmallVector FD{-1}; + SmallVector SR{S}; + static SmallVector FileName{""}; + if (auto Err = prepareTempFiles(FD, SR, FileName)) { + dbgs() << "Unable to create temporary file."; + return; + } + static ErrorOr 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 void OrderedChangedData::report( const OrderedChangedData &Before, const OrderedChangedData &After, @@ -2119,6 +2182,9 @@ 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 --- /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 --- /dev/null +++ b/llvm/test/Other/ChangeTesters/lit.local.cfg @@ -0,0 +1,2 @@ +if not os.path.exists('/bin/cat'): + config.unsupported = True