Index: llvm/include/llvm/Passes/StandardInstrumentations.h =================================================================== --- llvm/include/llvm/Passes/StandardInstrumentations.h +++ llvm/include/llvm/Passes/StandardInstrumentations.h @@ -273,6 +273,32 @@ bool same(const std::string &Before, const std::string &After) 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; +}; + // The following classes hold a representation of the IR for a change // reporter that uses string comparisons of the basic blocks // that are created using print (ie, similar to dump()). @@ -421,6 +447,7 @@ IRChangedPrinter PrintChangedIR; PseudoProbeVerifier PseudoProbeVerification; InLineChangePrinter PrintChangedDiff; + IRChangedTester ChangeTester; VerifyInstrumentation Verify; bool VerifyEach; Index: llvm/lib/Passes/StandardInstrumentations.cpp =================================================================== --- llvm/lib/Passes/StandardInstrumentations.cpp +++ llvm/lib/Passes/StandardInstrumentations.cpp @@ -119,8 +119,56 @@ DiffBinary("print-changed-diff-path", cl::Hidden, cl::init("diff"), cl::desc("system diff used by change reporters")); +// 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("test-changed", cl::Hidden, cl::init(""), + cl::desc("exe called with module IR after each pass that " + "changes it")); + namespace { +// The first M files have content (from \p SR) while the last N do not. +template +std::string prepareTempFiles(int (&FD)[M + N], const StringRef (&SR)[M], + std::string (&FileName)[M + N]) { + for (unsigned I = 0; I < M + N; ++I) { + if (FD[I] == -1) { + SmallVector SV; + std::error_code EC = + sys::fs::createTemporaryFile("tmpdiff", "txt", FD[I], SV); + if (EC) + return "Unable to create temporary file."; + FileName[I] = Twine(SV).str(); + } + // Only the first M files have initial content. + if (I < M) { + std::error_code EC = sys::fs::openFileForWrite(FileName[I], FD[I]); + if (EC) + return "Unable to open temporary file for writing."; + raw_fd_ostream OutStream(FD[I], /*shouldClose=*/true); + if (FD[I] == -1) + return "Error opening file for writing."; + OutStream << SR[I]; + } + } + return ""; +} + +template std::string cleanUpTempFiles(std::string (&FileName)[N]) { + for (unsigned I = 0; I < N; ++I) { + std::error_code EC = sys::fs::remove(FileName[I]); + if (EC) + return "Unable to remove temporary file."; + } + return ""; +} + // 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 @@ -128,34 +176,15 @@ std::string 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; + StringRef SR[NumFiles - 1]{Before, After}; static std::string FileName[NumFiles]; static int FD[NumFiles]{-1, -1, -1}; - for (unsigned I = 0; I < NumFiles; ++I) { - if (FD[I] == -1) { - SmallVector SV; - std::error_code EC = - sys::fs::createTemporaryFile("tmpdiff", "txt", FD[I], SV); - if (EC) - return "Unable to create temporary file."; - FileName[I] = Twine(SV).str(); - } - // The third file is used as the result of the diff. - if (I == NumFiles - 1) - break; - - std::error_code EC = sys::fs::openFileForWrite(FileName[I], FD[I]); - if (EC) - return "Unable to open temporary file for writing."; - - raw_fd_ostream OutStream(FD[I], /*shouldClose=*/true); - if (FD[I] == -1) - return "Error opening file for writing."; - OutStream << SR[I]; - } + std::string Err = prepareTempFiles(FD, SR, FileName); + if (Err != "") + return Err; static ErrorOr DiffExe = sys::findProgramByName(DiffBinary); if (!DiffExe) @@ -166,7 +195,8 @@ SmallString<128> ULF = formatv("--unchanged-line-format={0}", UnchangedLineFormat); - StringRef Args[] = {"-w", "-d", OLF, NLF, ULF, FileName[0], FileName[1]}; + StringRef Args[] = {DiffBinary, "-w", "-d", OLF, + NLF, ULF, FileName[0], FileName[1]}; Optional Redirects[] = {None, StringRef(FileName[2]), None}; int Result = sys::ExecuteAndWait(*DiffExe, Args, None, Redirects); if (Result < 0) @@ -178,12 +208,10 @@ else return "Unable to read result."; - // Clean up. - for (unsigned I = 0; I < NumFiles; ++I) { - std::error_code EC = sys::fs::remove(FileName[I]); - if (EC) - return "Unable to remove temporary file."; - } + Err = cleanUpTempFiles(FileName); + if (Err != "") + return Err; + return Diff; } @@ -565,6 +593,60 @@ return S1 == S2; } +IRChangedTester::~IRChangedTester() {} + +void IRChangedTester::registerCallbacks(PassInstrumentationCallbacks &PIC) { + if (TestChanged != "") + TextChangeReporter::registerRequiredCallbacks(PIC); +} + +void IRChangedTester::handleIR(const std::string &S, StringRef PassID) { + const unsigned NumFiles = 1; + StringRef SR[NumFiles]{S}; + // Store the body into a temporary file + static std::string FileName[NumFiles]; + static int FD[NumFiles]{-1}; + std::string Err = prepareTempFiles(FD, SR, FileName); + if (Err != "") { + dbgs() << Err; + 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; + } + + Err = cleanUpTempFiles(FileName); + if (Err != "") + dbgs() << Err; +} + +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, @@ -1237,6 +1319,7 @@ if (VerifyEach) Verify.registerCallbacks(PIC); PrintChangedDiff.registerCallbacks(PIC); + ChangeTester.registerCallbacks(PIC); } namespace llvm { Index: llvm/test/Other/test-changed-script.sh =================================================================== --- /dev/null +++ llvm/test/Other/test-changed-script.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +echo "***" $2 "***" +cat $1 Index: llvm/test/Other/test-changed.ll =================================================================== --- /dev/null +++ llvm/test/Other/test-changed.ll @@ -0,0 +1,103 @@ +; Simple checks of -test-changed=%S/test-changed-script.sh functionality +; +; Simple functionality check. +; RUN: opt -S -test-changed=%S/test-changed-script.sh -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 -test-changed=%S/test-changed-script.sh -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 -test-changed=%S/test-changed-script.sh -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 -test-changed=%S/test-changed-script.sh -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 -test-changed=%S/test-changed-script.sh -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 -test-changed=%S/test-changed-script.sh -passes="instsimplify,no-op-function" -filter-passes="NoOpFunctionPass" 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 -test-changed=%S/test-changed-script.sh -passes="instsimplify,no-op-function" -filter-passes="NoOpFunctionPass,InstSimplifyPass" 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 -test-changed=%S/test-changed-script.sh -passes="instsimplify,no-op-function" -filter-passes="NoOpFunctionPass,InstSimplifyPass" -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 -test-changed=%S/test-changed-script.sh -passes="instsimplify,no-op-function" -filter-passes="NoOpFunctionPass,InstSimplifyPass" -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 -test-changed=%S/test-changed-script.sh -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: *** Initial IR *** +; CHECK-SIMPLE-NEXT: ; ModuleID = {{.+}} +; CHECK-SIMPLE: *** InstSimplifyPass *** +; CHECK-SIMPLE-NEXT: define i32 @g() +; CHECK-SIMPLE: *** InstSimplifyPass *** +; CHECK-SIMPLE-NEXT: define i32 @f() + +; CHECK-FUNC-FILTER: *** Initial IR *** +; CHECK-FUNC-FILTER-NEXT: define i32 @f() +; CHECK-FUNC-FILTER: *** InstSimplifyPass *** +; CHECK-FUNC-FILTER-NEXT: define i32 @f() + +; CHECK-PRINT-MOD-SCOPE: *** Initial IR *** +; CHECK-PRINT-MOD-SCOPE-NEXT: ModuleID = {{.+}} +; CHECK-PRINT-MOD-SCOPE: *** InstSimplifyPass *** +; CHECK-PRINT-MOD-SCOPE-NEXT: ModuleID = {{.+}} +; CHECK-PRINT-MOD-SCOPE: *** InstSimplifyPass *** +; CHECK-PRINT-MOD-SCOPE-NEXT: ModuleID = {{.+}} + +; CHECK-FUNC-FILTER-MOD-SCOPE: *** Initial IR *** +; CHECK-FUNC-FILTER-MOD-SCOPE-NEXT: ; ModuleID = {{.+}} +; CHECK-FUNC-FILTER-MOD-SCOPE: *** InstSimplifyPass *** +; CHECK-FUNC-FILTER-MOD-SCOPE-NEXT: ModuleID = {{.+}} + +; CHECK-FILTER-MULT-FUNC: *** Initial IR *** +; CHECK-FILTER-MULT-FUNC-NEXT: define i32 @g() +; CHECK-FILTER-MULT-FUNC: *** InstSimplifyPass *** +; CHECK-FILTER-MULT-FUNC-NEXT: define i32 @g() +; CHECK-FILTER-MULT-FUNC: *** InstSimplifyPass *** +; CHECK-FILTER-MULT-FUNC-NEXT: define i32 @f() + +; CHECK-FILTER-PASSES: *** Initial IR *** +; CHECK-FILTER-PASSES-NEXT: define i32 @g() + +; CHECK-FILTER-MULT-PASSES: *** Initial IR *** +; CHECK-FILTER-MULT-PASSES-NEXT: define i32 @g() +; CHECK-FILTER-MULT-PASSES: *** InstSimplifyPass *** +; CHECK-FILTER-MULT-PASSES-NEXT: define i32 @g() +; CHECK-FILTER-MULT-PASSES: *** InstSimplifyPass *** +; CHECK-FILTER-MULT-PASSES-NEXT: define i32 @f() + +; CHECK-FILTER-FUNC-PASSES: *** Initial IR *** +; CHECK-FILTER-FUNC-PASSES-NEXT: define i32 @f() +; CHECK-FILTER-FUNC-PASSES: *** InstSimplifyPass *** +; CHECK-FILTER-FUNC-PASSES-NEXT: define i32 @f() + +; CHECK-FILTER-FUNC-PASSES-MOD-SCOPE: *** Initial IR *** +; CHECK-FILTER-FUNC-PASSES-MOD-SCOPE-NEXT: ; ModuleID = {{.+}} +; CHECK-FILTER-FUNC-PASSES-MOD-SCOPE: *** InstSimplifyPass *** +; CHECK-FILTER-FUNC-PASSES-MOD-SCOPE-NEXT: ModuleID = {{.+}} + +; CHECK-MULT-PASSES-FILTER-FUNC: *** Initial IR *** +; CHECK-MULT-PASSES-FILTER-FUNC-NEXT: define i32 @f() +; CHECK-MULT-PASSES-FILTER-FUNC: *** InstSimplifyPass *** +; CHECK-MULT-PASSES-FILTER-FUNC-NEXT: define i32 @f()