Index: llvm/include/llvm/Passes/StandardInstrumentations.h =================================================================== --- llvm/include/llvm/Passes/StandardInstrumentations.h +++ llvm/include/llvm/Passes/StandardInstrumentations.h @@ -137,12 +137,12 @@ // 6. When a pass is run on an IR that is not interesting (based on options). // 7. When a pass is ignored (pass manager or adapter pass). // 8. To compare two IR representations (of type \p T). -template class ChangePrinter { +template class ChangeReporter { protected: - ChangePrinter() {} + ChangeReporter() {} public: - virtual ~ChangePrinter(); + virtual ~ChangeReporter(); // Determine if this pass/IR is interesting and if so, save the IR // otherwise it is left on the stack without data. @@ -152,7 +152,37 @@ // Handle the situation where a pass is invalidated. void handleInvalidatedPass(StringRef PassID); + // Register standard callbacks that call out on the lambdas + void registerCallbacks(llvm::PassInstrumentationCallbacks &PIC) { + PIC.registerBeforePassCallback([this](llvm::StringRef P, llvm::Any IR) { + saveIRBeforePass(IR, P); + return true; + }); + + PIC.registerAfterPassCallback( + [this](llvm::StringRef P, llvm::Any IR, + const llvm::PreservedAnalyses &) { handleIRAfterPass(IR, P); }); + PIC.registerAfterPassInvalidatedCallback( + [this](llvm::StringRef P, const llvm::PreservedAnalyses &) { + handleInvalidatedPass(P); + }); + } + protected: + // Return true when this is a pass for which changes should be ignored + inline bool isIgnored(llvm::StringRef PassID); + + // Return true when this is a defined function for which printing + // of changes is desired. + inline bool isInterestingFunction(const llvm::Function &F); + + // Return true when this is a pass for which printing of changes is desired. + inline bool isInterestingPass(llvm::StringRef PassID); + + // Return true when this is a pass on IR for which printing + // of changes is desired. + inline bool isInteresting(llvm::Any IR, llvm::StringRef PassID); + // Called on the first IR processed. virtual void handleInitialIR(Any IR) = 0; // Called before and after a pass to get the representation of the IR. @@ -179,38 +209,265 @@ bool InitialIR = true; }; +// An abstract template base class that handles printing banners and +// reporting when things have not changed or are filtered out. +template +class TextChangeReporter : public ChangeReporter { +protected: + TextChangeReporter(); + + // Print a module dump of the first IR that is changed. + void handleInitialIR(llvm::Any IR) override; + // Report that the IR was omitted because it did not change. + void omitAfter(llvm::StringRef PassID, std::string &Name) override; + // Report that the pass was invalidated. + void handleInvalidated(llvm::StringRef PassID) override; + // Report that the IR was filtered out. + void handleFiltered(llvm::StringRef PassID, std::string &Name) override; + // Report that the pass was ignored. + void handleIgnored(llvm::StringRef PassID, std::string &Name) override; + // Make substitutions in \p S suitable for reporting changes + // after the pass and then print it. + + llvm::raw_ostream &Out; +}; + // A change printer based on the string representation of the IR as created // by unwrapAndPrint. The string representation is stored in a std::string // to preserve it as the IR changes in each pass. Note that the banner is // included in this representation but it is massaged before reporting. -class IRChangePrinter : public ChangePrinter { +class IRChangedPrinter : public TextChangeReporter { public: - IRChangePrinter(); - ~IRChangePrinter() override; + IRChangedPrinter() {} + ~IRChangedPrinter() override; void registerCallbacks(PassInstrumentationCallbacks &PIC); protected: - // Called on the first IR processed. - void handleInitialIR(Any IR) override; // Called before and after a pass to get the representation of the IR. void generateIRRepresentation(Any IR, StringRef PassID, std::string &Output) override; - // Called when the pass is not iteresting. - void omitAfter(StringRef PassID, std::string &Name) override; // Called when an interesting IR has changed. void handleAfter(StringRef PassID, std::string &Name, const std::string &Before, const std::string &After, Any) override; - // Called when an interesting pass is invalidated. - void handleInvalidated(StringRef PassID) override; - // Called when the IR or pass is not interesting. - void handleFiltered(StringRef PassID, std::string &Name) override; - // Called when an ignored pass is encountered. - void handleIgnored(StringRef PassID, std::string &Name) override; // Called to compare the before and after representations of the IR. bool same(const std::string &Before, const std::string &After) override; +}; + +// The following abstract template base classes contain the necessary code to +// create a change reporter that uses string comparisons of the basic blocks +// that are created using Value::print (ie, similar to dump()). +// These classes allow one to associate extra data with each basic block +// and the structs representing the basic blocks are grouped into +// the structs representing the functions. Essentially, overrides +// in the classes derived from ChangeReporter use a class derived from +// IRComparer to compare representations of IRs. The overrides are called +// when changes are detected after each pass to generate the output while the +// mechanics of detecting changes is handled in the base classes. The +// saving of extra data that may be desired by the overrides is done +// in a class derived from IRComparer, as is the actual generation +// of the output in response to changes to the IR. +// These change reporters work on basic blocks as the basic unit, meaning +// that comparisons between the before and after and made by comparing +// basic blocks and showing the differences between basic blocks. Basic +// blocks are matched from comparison using the name of the basic block +// so if a basic block is renamed, it will be considered to be deleted +// and the renamed block as added since there are no basic blocks with +// matching names. +// These classes respect the filtering of passes and functions using +// -filter-passes and -filter-print-funcs. + +// 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 BlockDataTemplate { +public: + // Note that the constructor will call a static function + // BlockDataType::initialize(BlockDataType &, BasicBlock &) + // to fill in the Data member. + BlockDataTemplate(const llvm::BasicBlock &B); + + bool operator!=(const BlockDataTemplate &that) const { + return Body.compare(that.Body) != 0; + } - raw_ostream &Out; + // Return the label of the represented basic block. + llvm::StringRef getLabel() const { return Label; } + // Return the string representation of the basic block. + llvm::StringRef getBody() const { return Body; } + + // Return the associated data + const BlockDataType &getData() const { return Data; } + +protected: + std::string Label; + std::string Body; + + // Extra data associated with a basic block + BlockDataType Data; +}; + +// The data saved for comparing functions. +template class FuncDataTemplate { +public: + FuncDataTemplate(const llvm::Function &F); + + // Create data for a missing function that is present in the other IR + FuncDataTemplate(llvm::StringRef Name) { EntryBlockName = Name.str(); } + + bool operator==(const FuncDataTemplate &that) const; + + bool operator!=(const FuncDataTemplate &that) const { + return !(*this == that); + } + + // Iterator over the saved basic block data + typename llvm::StringMap>::const_iterator + begin() const { + return Blocks.begin(); + } + typename llvm::StringMap>::const_iterator + end() const { + return Blocks.end(); + } + + // Return the name of the entry block + std::string getEntryBlockName() const { return EntryBlockName; } + + // Return the block data for the basic block with label \p S. + const BlockDataTemplate * + getBlockData(const llvm::StringRef S) const { + return (Blocks.count(S) == 1) ? &Blocks.find(S)->getValue() : nullptr; + } + +protected: + std::string EntryBlockName; + llvm::StringMap> Blocks; +}; + +// A map of names to the saved data. +template +class IRDataTemplate : public llvm::StringMap { +public: + IRDataTemplate() = default; + bool operator==(const IRDataTemplate &that) const; + bool operator!=(const IRDataTemplate &that) const { return !(*this == that); } +}; + +enum IRChangeDiffType { InBefore, InAfter, IsCommon }; + +// Abstract template base class for a class that compares two IRs. The +// class is created with the 2 IRs to compare and then compare is called. +// The static function analyzeIR is used to build up the IR representation. +template +class IRComparer { +protected: + IRComparer(const IRDataType &Before, const IRDataType &After, unsigned N); + +public: + virtual ~IRComparer(); + + // Compare the 2 IRs. In the case of a module IR, delegate to + // handleFunctionCompare in the derived class for each function; + // otherwise delegate to handleCompare in the derived class. + void compare(llvm::Any IR, llvm::StringRef Prefix, llvm::StringRef PassID, + llvm::StringRef Name); + // Analyze \p IR and save the data in \p Data. + static bool analyzeIR(llvm::Any IR, IRDataType &Data); + +protected: + // Return the module when that is the appropriate level of + // comparison for \p IR. + static const llvm::Module *getModule(llvm::Any IR); + // Generate the data for \p F into \p Data. + static bool generateFunctionData(IRDataType &Data, const llvm::Function &F); + + // Called to handle the compare of function when IR unit is a function. + virtual void handleSingleFunctionCompare(llvm::StringRef Name, + llvm::StringRef Prefix, + llvm::StringRef PassID, unsigned, + unsigned *, + const FuncDataType &Before, + const FuncDataType &After) = 0; + // Called to handle the compare of function when IR unit is a module. + virtual void handleFunctionInModuleCompare(llvm::StringRef Name, + llvm::StringRef Prefix, + llvm::StringRef PassID, unsigned, + unsigned *, + const FuncDataType &Before, + const FuncDataType &After) = 0; + + const IRDataType &Before; + const IRDataType &After; + unsigned N; +}; + +// An empty class that satisfies the requirements for being a template +// argument for the IRComparer template base class. +class EmptyData { +public: + static void initialize(EmptyData &, const llvm::BasicBlock &) {} +}; + +using EmptyDataBlockData = BlockDataTemplate; +using EmptyDataFuncData = FuncDataTemplate; +using EmptyDataIRData = IRDataTemplate; + +// A class that compares two IRs of type EmptyDataIRData and does a +// linux diff between them. The added lines are prefixed with a '+', +// the removed lines are prefixed with a '-' and unchanged lines are +// prefixed with a space (to have things line up). The functions +// handleCompare and handleFunctionCompare are called by the base +// class with the appropriate parameters. +class InLineComparer : public IRComparer { +public: + InLineComparer(llvm::raw_ostream &OS, const EmptyDataIRData &Before, + const EmptyDataIRData &After) + : IRComparer(Before, After, 0), Out(OS) {} + ~InLineComparer() override; + + // Called to handle the compare of function when IR unit is a function. + void handleSingleFunctionCompare(llvm::StringRef Name, llvm::StringRef Prefix, + llvm::StringRef PassID, unsigned, unsigned *, + const EmptyDataFuncData &Before, + const EmptyDataFuncData &After) override; + // Called to handle the compare of function when IR unit is a module. + void handleFunctionInModuleCompare(llvm::StringRef Name, + llvm::StringRef Prefix, + llvm::StringRef PassID, unsigned, + unsigned *, + const EmptyDataFuncData &Before, + const EmptyDataFuncData &After) override; + +protected: + llvm::raw_ostream &Out; +}; + +// A change printer that prints out in-line differences in the basic +// blocks. It uses an InlineComparer to do the comparison so it shows +// the differences prefixed with '-' and '+' for code that is removed +// and added, respectively. Changes to the IR that do not affect basic +// blocks are not reported as having changed the IR. The option +// -print-module-scope does not affect this change reporter. +class InLineChangePrinter : public TextChangeReporter { +public: + InLineChangePrinter() {} + ~InLineChangePrinter() override; + void registerCallbacks(PassInstrumentationCallbacks &PIC); + +protected: + // Create a representation of the IR. + virtual void generateIRRepresentation(llvm::Any IR, llvm::StringRef PassID, + EmptyDataIRData &Output) override; + + // Called when an interesting IR has changed. + virtual void handleAfter(StringRef PassID, std::string &Name, + const EmptyDataIRData &Before, + const EmptyDataIRData &After, Any) override; + // Called to compare the before and after representations of the IR. + virtual bool same(const EmptyDataIRData &Before, + const EmptyDataIRData &After) override; }; /// This class provides an interface to register all the standard pass @@ -221,7 +478,8 @@ TimePassesHandler TimePasses; OptNoneInstrumentation OptNone; PreservedCFGCheckerInstrumentation PreservedCFGChecker; - IRChangePrinter PrintChangedIR; + IRChangedPrinter PrintChangedIR; + InLineChangePrinter PrintChanges; public: StandardInstrumentations(bool DebugLogging) : PrintPass(DebugLogging) {} @@ -230,6 +488,18 @@ TimePassesHandler &getTimePasses() { return TimePasses; } }; + +extern template class ChangeReporter; +extern template class TextChangeReporter; + +extern template class BlockDataTemplate; +extern template class FuncDataTemplate; +extern template class IRDataTemplate; +extern template class ChangeReporter; +extern template class TextChangeReporter; +extern template class IRComparer; + } // namespace llvm #endif Index: llvm/lib/Passes/StandardInstrumentations.cpp =================================================================== --- llvm/lib/Passes/StandardInstrumentations.cpp +++ llvm/lib/Passes/StandardInstrumentations.cpp @@ -79,9 +79,76 @@ cl::desc("Only consider IR changes for passes whose names " "match for the print-changed option"), cl::CommaSeparated, cl::Hidden); +// An option -print-changes which prints in line differences of +// changed IR as they progress through the pipeline. The differences +// are presented in a form similar to a patch. The lines that are removed +// and added are prefixed with '-' and '+', respectively. The +// -filter-print-funcs and -filter-passes can be used to filter the output. +// This reporter relies on the linux diff utility to do comparisons and +// insert the prefixes. For systems that do not have the necessary +// facilities, the error message will be shown in place of the expected output. +static cl::opt PrintChanges( + "print-changes", cl::Hidden, cl::init(false), + cl::desc("Print in-line differences of changes made by a pass")); namespace { +// Perform a linux 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 of the diff. +std::string doLinuxDiff(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. + static std::string FileName[2]; + static int FD[2]{-1, -1}; + for (unsigned I = 0; I < 2; ++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(); + } + + std::error_code EC = sys::fs::openFileForWrite(FileName[I], FD[I]); + if (EC) + return "Unable to open temporary file for writing."; + + llvm::raw_fd_ostream OutStream(FD[I], /*shouldClose=*/true); + if (FD[I] == -1) + return "Error opening file for writing."; + OutStream << SR[I]; + } + + SmallString<512> DiffCall = + formatv("diff -w -d --old-line-format='{2}' --new-line-format='{3}' " + "--unchanged-line-format='{4}' {0} {1}", + FileName[0], FileName[1], OldLineFormat, NewLineFormat, + UnchangedLineFormat); + std::string Diff; + + std::array Buffer; + FILE *Pipe = popen(DiffCall.c_str(), "r"); + if (!Pipe) + return "Error opening pipe."; + while (fgets(Buffer.data(), Buffer.size(), Pipe) != nullptr) + Diff += Buffer.data(); + pclose(Pipe); + + // Clean up. + for (unsigned I = 0; I < 2; ++I) { + std::error_code EC = sys::fs::remove(FileName[I]); + if (EC) + return "Unable to remove temporary file."; + } + return Diff; +} + /// Extracting Module out of \p IR unit. Also fills a textual description /// of \p IR for use in header when printing. Optional> @@ -232,20 +299,26 @@ llvm_unreachable("Unknown wrapped IR type"); } -// Return true when this is a pass for which changes should be ignored -inline bool isIgnored(StringRef PassID) { +} // namespace + +template +ChangeReporter::~ChangeReporter() { + assert(BeforeStack.empty() && "Problem with Change Printer stack."); +} + +template +bool ChangeReporter::isIgnored(StringRef PassID) { return isSpecialPass(PassID, {"PassManager", "PassAdaptor", "AnalysisManagerProxy"}); } -// Return true when this is a defined function for which printing -// of changes is desired. -inline bool isInterestingFunction(const Function &F) { +template +bool ChangeReporter::isInterestingFunction(const Function &F) { return llvm::isFunctionInPrintList(F.getName()); } -// Return true when this is a pass for which printing of changes is desired. -inline bool isInterestingPass(StringRef PassID) { +template +bool ChangeReporter::isInterestingPass(StringRef PassID) { if (isIgnored(PassID)) return false; @@ -256,7 +329,8 @@ // Return true when this is a pass on IR for which printing // of changes is desired. -bool isInteresting(Any IR, StringRef PassID) { +template +bool ChangeReporter::isInteresting(Any IR, StringRef PassID) { if (!isInterestingPass(PassID)) return false; if (any_isa(IR)) @@ -264,10 +338,8 @@ return true; } -} // namespace - template -void ChangePrinter::saveIRBeforePass(Any IR, StringRef PassID) { +void ChangeReporter::saveIRBeforePass(Any IR, StringRef PassID) { // Always need to place something on the stack because invalidated passes // are not given the IR so it cannot be determined whether the pass was for // something that was filtered out. @@ -287,7 +359,7 @@ } template -void ChangePrinter::handleIRAfterPass(Any IR, StringRef PassID) { +void ChangeReporter::handleIRAfterPass(Any IR, StringRef PassID) { assert(!BeforeStack.empty() && "Unexpected empty stack encountered."); std::string Name; @@ -299,7 +371,7 @@ if (auto UM = unwrapModule(IR)) Name = UM->second; } - if (Name.empty()) + if (Name == "") Name = " (module)"; if (isIgnored(PassID)) @@ -323,7 +395,7 @@ } template -void ChangePrinter::handleInvalidatedPass(StringRef PassID) { +void ChangeReporter::handleInvalidatedPass(StringRef PassID) { assert(!BeforeStack.empty() && "Unexpected empty stack encountered."); // Always flag it as invalidated as we cannot determine when @@ -334,34 +406,53 @@ BeforeStack.pop_back(); } -template ChangePrinter::~ChangePrinter() { - assert(BeforeStack.empty() && "Problem with Change Printer stack."); +template +BlockDataTemplate::BlockDataTemplate(const llvm::BasicBlock &B) + : Label(B.getName().str()), Data() { + llvm::raw_string_ostream SS(Body); + B.Value::print(SS, true); + BlockDataType::initialize(Data, B); } -IRChangePrinter::IRChangePrinter() : Out(dbgs()) {} - -IRChangePrinter::~IRChangePrinter() {} - -void IRChangePrinter::registerCallbacks(PassInstrumentationCallbacks &PIC) { - if (!PrintChanged) - return; +template +FuncDataTemplate::FuncDataTemplate(const llvm::Function &F) { + EntryBlockName = F.getEntryBlock().getName().str(); + for (auto &B : F) + Blocks.insert({B.getName(), B}); +} - PIC.registerBeforePassCallback([this](StringRef P, Any IR) { - saveIRBeforePass(IR, P); - return true; - }); +template +bool FuncDataTemplate::operator==(const FuncDataTemplate &that) const { + for (auto &B : that) { + llvm::StringRef Label = B.getKey(); + if (Blocks.count(Label) == 0) + return false; + const BlockDataTemplate &ThisData = Blocks.find(Label)->getValue(); + const BlockDataTemplate &ThatData = B.getValue(); + if (ThisData != ThatData) + return false; + } + return true; +} - PIC.registerAfterPassCallback( - [this](StringRef P, Any IR, const PreservedAnalyses &) { - handleIRAfterPass(IR, P); - }); - PIC.registerAfterPassInvalidatedCallback( - [this](StringRef P, const PreservedAnalyses &) { - handleInvalidatedPass(P); - }); +template +bool IRDataTemplate::operator==(const IRDataTemplate &that) const { + if (llvm::StringMap::size() != that.size()) + return false; + for (auto &F : *this) { + llvm::StringRef Key = F.getKey(); + if (that.count(Key) == 0 || F.getValue() != that.find(Key)->getValue()) + return false; + } + return true; } -void IRChangePrinter::handleInitialIR(Any IR) { +template +TextChangeReporter::TextChangeReporter() + : ChangeReporter(), Out(dbgs()) {} + +template +void TextChangeReporter::handleInitialIR(Any IR) { // Always print the module. // Unwrap and print directly to avoid filtering problems in general routines. auto UnwrappedModule = unwrapModule(IR, /*Force=*/true); @@ -371,55 +462,149 @@ /*ShouldPreserveUseListOrder=*/true); } -void IRChangePrinter::generateIRRepresentation(Any IR, StringRef PassID, - std::string &Output) { - raw_string_ostream OS(Output); - // use the after banner for all cases so it will match - SmallString<20> Banner = formatv("*** IR Dump After {0} ***", PassID); - unwrapAndPrint(OS, IR, Banner, llvm::forcePrintModuleIR(), - /*Brief=*/false, /*ShouldPreserveUseListOrder=*/true); - OS.str(); -} - -void IRChangePrinter::omitAfter(StringRef PassID, std::string &Name) { +template +void TextChangeReporter::omitAfter(StringRef PassID, + std::string &Name) { Out << formatv("*** IR Dump After {0}{1} omitted because no change ***\n", PassID, Name); } -void IRChangePrinter::handleAfter(StringRef PassID, std::string &Name, - const std::string &Before, - const std::string &After, Any) { - assert(After.find("*** IR Dump") == 0 && "Unexpected banner format."); - StringRef AfterRef = After; - StringRef Banner = - AfterRef.take_until([](char C) -> bool { return C == '\n'; }); - Out << Banner; - - // LazyCallGraph::SCC already has "(scc:..." in banner so only add - // in the name if it isn't already there. - if (Name.substr(0, 6) != " (scc:" && !llvm::forcePrintModuleIR()) - Out << Name; - - Out << After.substr(Banner.size()); -} - -void IRChangePrinter::handleInvalidated(StringRef PassID) { +template +void TextChangeReporter::handleInvalidated(StringRef PassID) { Out << formatv("*** IR Pass {0} invalidated ***\n", PassID); } -void IRChangePrinter::handleFiltered(StringRef PassID, std::string &Name) { +template +void TextChangeReporter::handleFiltered(StringRef PassID, + std::string &Name) { SmallString<20> Banner = formatv("*** IR Dump After {0}{1} filtered out ***\n", PassID, Name); Out << Banner; } -void IRChangePrinter::handleIgnored(StringRef PassID, std::string &Name) { +template +void TextChangeReporter::handleIgnored(StringRef PassID, + std::string &Name) { Out << formatv("*** IR Pass {0}{1} ignored ***\n", PassID, Name); } -bool IRChangePrinter::same(const std::string &Before, - const std::string &After) { - return Before == After; +template +IRComparer::IRComparer( + const IRDataType &Before, const IRDataType &After, unsigned N) + : Before(Before), After(After), N(N) {} + +template +IRComparer::~IRComparer() {} + +template +void IRComparer::compare( + llvm::Any IR, llvm::StringRef Prefix, llvm::StringRef PassID, + llvm::StringRef Name) { + if (!getModule(IR)) { + // Not a module so just handle the single function. + assert(Before.size() == 1 && "Expected only one function."); + assert(After.size() == 1 && "Expected only one function."); + ((ThisT *)this) + ->handleSingleFunctionCompare(Name, Prefix, PassID, N, nullptr, + Before.begin()->getValue(), + After.begin()->getValue()); + return; + } + // Determine whether functions are common or not. + llvm::StringMap Funcs; + for (auto &B : Before) + Funcs.insert({B.getKey(), InBefore}); + for (auto &A : After) + if (Funcs.count(A.getKey()) == 1) + Funcs[A.getKey()] = IsCommon; + else + Funcs.insert({A.getKey(), InAfter}); + + // Set up an appropriate numbering system. + unsigned Minor = 0; + + for (auto &F : Funcs) { + Name = F.getKey(); + IRChangeDiffType CDDT = F.getValue(); + // If the function isn't common, create an empty version for the compare. + if (CDDT == InBefore) { + FuncDataType MissingFunc( + Before.find(Name)->getValue().getEntryBlockName()); + ((ThisT *)this) + ->handleFunctionInModuleCompare(Name, Prefix, PassID, N, &Minor, + Before.find(Name)->getValue(), + MissingFunc); + } else if (CDDT == InAfter) { + FuncDataType MissingFunc( + After.find(Name)->getValue().getEntryBlockName()); + ((ThisT *)this) + ->handleFunctionInModuleCompare(Name, Prefix, PassID, N, &Minor, + MissingFunc, + After.find(Name)->getValue()); + } else { + assert(CDDT == IsCommon && "Unexpected Diff type."); + ((ThisT *)this) + ->handleFunctionInModuleCompare(Name, Prefix, PassID, N, &Minor, + Before.find(Name)->getValue(), + After.find(Name)->getValue()); + } + ++Minor; + } +} + +template +bool IRComparer::analyzeIR( + llvm::Any IR, IRDataType &Data) { + if (const llvm::Module *M = getModule(IR)) { + // Create data for each existing/interesting function in the module. + bool OutputExists = false; + for (const llvm::Function &F : *M) + OutputExists = generateFunctionData(Data, F) | OutputExists; + return OutputExists; + } + + const llvm::Function *F = nullptr; + if (llvm::any_isa(IR)) + F = llvm::any_cast(IR); + else { + assert(llvm::any_isa(IR) && "Unknown IR unit."); + const llvm::Loop *L = llvm::any_cast(IR); + F = L->getHeader()->getParent(); + } + assert(F && "Unknown IR unit."); + return generateFunctionData(Data, *F); +} + +template +const llvm::Module * +IRComparer::getModule( + llvm::Any IR) { + if (llvm::any_isa(IR)) + return llvm::any_cast(IR); + if (llvm::any_isa(IR)) { + const llvm::LazyCallGraph::SCC *C = + llvm::any_cast(IR); + for (const llvm::LazyCallGraph::Node &N : *C) + return N.getFunction().getParent(); + } + return nullptr; +} + +template +bool IRComparer::generateFunctionData(IRDataType &Data, + const llvm::Function &F) { + if (!F.isDeclaration() && llvm::isFunctionInPrintList(F.getName())) { + Data.insert({F.getName(), F}); + return true; + } + return false; } PrintIRInstrumentation::~PrintIRInstrumentation() { @@ -724,6 +909,107 @@ }); } +IRChangedPrinter::~IRChangedPrinter() {} + +void IRChangedPrinter::registerCallbacks(PassInstrumentationCallbacks &PIC) { + if (PrintChanged) + TextChangeReporter::registerCallbacks(PIC); +} + +void IRChangedPrinter::generateIRRepresentation(Any IR, StringRef PassID, + std::string &Output) { + raw_string_ostream OS(Output); + // use the after banner for all cases so it will match + SmallString<20> Banner = formatv("*** IR Dump After {0} ***", PassID); + unwrapAndPrint(OS, IR, Banner, llvm::forcePrintModuleIR(), + /*Brief=*/false, /*ShouldPreserveUseListOrder=*/true); + + OS.str(); +} + +void IRChangedPrinter::handleAfter(StringRef PassID, std::string &Name, + const std::string &Before, + const std::string &After, Any IR) { + assert(After.find("*** IR Dump") == 0 && "Unexpected banner format."); + StringRef S = After; + StringRef Banner = S.take_until([](char C) -> bool { return C == '\n'; }); + Out << Banner; + + // LazyCallGraph::SCC already has "(scc:..." in banner so only + // add in the name if it isn't already there. + if (Name.substr(0, 6).compare(" (scc:") != 0 && !llvm::forcePrintModuleIR()) + Out << Name; + Out << S.substr(Banner.size()); +} + +bool IRChangedPrinter::same(const std::string &S1, const std::string &S2) { + return S1.compare(S2) == 0; +} + +InLineChangePrinter::~InLineChangePrinter() {} + +void InLineChangePrinter::generateIRRepresentation(Any IR, StringRef PassID, + EmptyDataIRData &D) { + InLineComparer::analyzeIR(IR, D); +} + +void InLineChangePrinter::handleAfter(StringRef PassID, std::string &Name, + const EmptyDataIRData &Before, + const EmptyDataIRData &After, Any IR) { + if (Name == "") + Name = " (module)"; + SmallString<20> Banner = + formatv("*** IR Dump After {0} ***{1}\n", PassID, Name); + Out << Banner; + InLineComparer(Out, Before, After).compare(IR, "", PassID, Name); + Out << "\n"; +} + +bool InLineChangePrinter::same(const EmptyDataIRData &D1, + const EmptyDataIRData &D2) { + return D1 == D2; +} + +InLineComparer::~InLineComparer() {} + +void InLineComparer::handleSingleFunctionCompare( + llvm::StringRef, llvm::StringRef Prefix, llvm::StringRef PassID, unsigned, + unsigned *, const EmptyDataFuncData &Before, + const EmptyDataFuncData &After) { + for (auto &B : Before) { + const EmptyDataBlockData &BeforeBD = B.getValue(); + const EmptyDataBlockData *BD = After.getBlockData(BeforeBD.getLabel()); + if (BD) + // This block is in both + Out << doLinuxDiff(BeforeBD.getBody(), BD->getBody(), "-%l\n", "+%l\n", + " %l\n"); + else + // This has been removed + Out << doLinuxDiff(BeforeBD.getBody(), "\n", "-%l\n", "+%l\n", " %l\n"); + } + for (auto &B : After) { + const EmptyDataBlockData &AfterBD = B.getValue(); + const EmptyDataBlockData *BD = Before.getBlockData(AfterBD.getLabel()); + if (!BD) + // This block is new + Out << doLinuxDiff("\n", AfterBD.getBody(), "-%l\n", "+%l\n", " %l\n"); + } +} + +void InLineComparer::handleFunctionInModuleCompare( + llvm::StringRef Name, llvm::StringRef Prefix, llvm::StringRef PassID, + unsigned, unsigned *, const EmptyDataFuncData &Before, + const EmptyDataFuncData &After) { + Out << "\n*** IR for function " << Name << " ***\n"; + InLineComparer::handleSingleFunctionCompare(Name, Prefix, PassID, 0, nullptr, + Before, After); +} + +void InLineChangePrinter::registerCallbacks(PassInstrumentationCallbacks &PIC) { + if (PrintChanges) + TextChangeReporter::registerCallbacks(PIC); +} + void StandardInstrumentations::registerCallbacks( PassInstrumentationCallbacks &PIC) { PrintIR.registerCallbacks(PIC); @@ -732,4 +1018,20 @@ OptNone.registerCallbacks(PIC); PreservedCFGChecker.registerCallbacks(PIC); PrintChangedIR.registerCallbacks(PIC); + PrintChanges.registerCallbacks(PIC); } + +namespace llvm { + +template class ChangeReporter; +template class TextChangeReporter; + +template class BlockDataTemplate; +template class FuncDataTemplate; +template class IRDataTemplate; +template class ChangeReporter; +template class TextChangeReporter; +template class IRComparer; + +} // namespace llvm Index: llvm/test/Other/change-printer.ll =================================================================== --- llvm/test/Other/change-printer.ll +++ llvm/test/Other/change-printer.ll @@ -34,6 +34,36 @@ ; 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 -print-changed -passes="instsimplify,instsimplify" -filter-print-funcs=f 2>&1 -o /dev/null < %s | FileCheck %s --check-prefix=CHECK-MULT-PASSES-FILTER-FUNC +; +; print-changes tests +; +; Simple functionality check. +; RUN: opt -S -print-changes -passes=instsimplify 2>&1 -o /dev/null < %s | FileCheck %s --check-prefix=CHECK-CHANGES-SIMPLE +; +; Check that only the passes that change the IR are printed and that the +; others (including g) are filtered out. +; RUN: opt -S -print-changes -passes=instsimplify -filter-print-funcs=f 2>&1 -o /dev/null < %s | FileCheck %s --check-prefix=CHECK-CHANGES-FUNC-FILTER +; +; Check that the reporting of IRs respects is not affected by +; -print-module-scope +; RUN: opt -S -print-changes -passes=instsimplify -print-module-scope 2>&1 -o /dev/null < %s | FileCheck %s --check-prefix=CHECK-CHANGES-PRINT-MOD-SCOPE +; +; Check that reporting of multiple functions happens +; RUN: opt -S -print-changes -passes=instsimplify -filter-print-funcs="f,g" 2>&1 -o /dev/null < %s | FileCheck %s --check-prefix=CHECK-CHANGES-FILTER-MULT-FUNC +; +; Check that the reporting of IRs respects -filter-passes +; RUN: opt -S -print-changes -passes="instsimplify,no-op-function" -filter-passes="NoOpFunctionPass" 2>&1 -o /dev/null < %s | FileCheck %s --check-prefix=CHECK-CHANGES-FILTER-PASSES +; +; Check that the reporting of IRs respects -filter-passes with multiple passes +; RUN: opt -S -print-changes -passes="instsimplify,no-op-function" -filter-passes="NoOpFunctionPass,InstSimplifyPass" 2>&1 -o /dev/null < %s | FileCheck %s --check-prefix=CHECK-CHANGES-FILTER-MULT-PASSES +; +; Check that the reporting of IRs respects both -filter-passes and -filter-print-funcs +; RUN: opt -S -print-changes -passes="instsimplify,no-op-function" -filter-passes="NoOpFunctionPass,InstSimplifyPass" -filter-print-funcs=f 2>&1 -o /dev/null < %s | FileCheck %s --check-prefix=CHECK-CHANGES-FILTER-FUNC-PASSES +; +; Check that repeated passes that change the IR are printed and that the +; others (including g) are filtered out. Note that only the first time +; instsimplify is run on f will result in changes +; RUN: opt -S -print-changes -passes="instsimplify,instsimplify" -filter-print-funcs=f 2>&1 -o /dev/null < %s | FileCheck %s --check-prefix=CHECK-CHANGES-MULT-PASSES-FILTER-FUNC define i32 @g() { entry: @@ -126,3 +156,74 @@ ; CHECK-MULT-PASSES-FILTER-FUNC: *** IR Dump After InstSimplifyPass *** (function: f) ; CHECK-MULT-PASSES-FILTER-FUNC-NEXT: define i32 @f() ; CHECK-MULT-PASSES-FILTER-FUNC: *** IR Dump After InstSimplifyPass (function: f) omitted because no change *** + +; CHECK-CHANGES-SIMPLE: *** IR Dump At Start: *** +; CHECK-CHANGES-SIMPLE: ModuleID = {{.+}} +; CHECK-CHANGES-SIMPLE: *** IR Dump After VerifierPass (module) omitted because no change *** +; CHECK-CHANGES-SIMPLE: *** IR Dump After InstSimplifyPass *** (function: g) +; CHECK-CHANGES-SIMPLE-NOT: ModuleID = {{.+}} +; CHECK-CHANGES-SIMPLE: entry: +; CHECK-CHANGES-SIMPLE: *** IR Pass PassManager{{.*}} (function: g) ignored *** +; CHECK-CHANGES-SIMPLE: *** IR Dump After InstSimplifyPass *** (function: f) +; CHECK-CHANGES-SIMPLE-NOT: ModuleID = {{.+}} +; CHECK-CHANGES-SIMPLE: entry: +; CHECK-CHANGES-SIMPLE: *** IR Pass PassManager{{.*}} (function: f) ignored *** +; CHECK-CHANGES-SIMPLE: *** IR Pass ModuleToFunctionPassAdaptor<{{.*}}PassManager{{.*}}> (module) ignored *** +; CHECK-CHANGES-SIMPLE: *** IR Dump After VerifierPass (module) omitted because no change *** +; CHECK-CHANGES-SIMPLE: *** IR Dump After PrintModulePass (module) omitted because no change *** + +; CHECK-CHANGES-FUNC-FILTER: *** IR Dump At Start: *** +; CHECK-CHANGES-FUNC-FILTER-NEXT: ; ModuleID = {{.+}} +; CHECK-CHANGES-FUNC-FILTER: *** IR Dump After InstSimplifyPass (function: g) filtered out *** +; CHECK-CHANGES-FUNC-FILTER: *** IR Dump After InstSimplifyPass *** (function: f) +; CHECK-CHANGES-FUNC-FILTER-NOT: ModuleID = {{.+}} +; CHECK-CHANGES-FUNC-FILTER: entry: + +; CHECK-CHANGES-PRINT-MOD-SCOPE: *** IR Dump At Start: *** +; CHECK-CHANGES-PRINT-MOD-SCOPE: ModuleID = {{.+}} +; CHECK-CHANGES-PRINT-MOD-SCOPE: *** IR Dump After InstSimplifyPass *** (function: g) +; CHECK-CHANGES-PRINT-MOD-SCOPE-NOT: ModuleID = {{.+}} +; CHECK-CHANGES-PRINT-MOD-SCOPE: entry: +; CHECK-CHANGES-PRINT-MOD-SCOPE: *** IR Dump After InstSimplifyPass *** (function: f) +; CHECK-CHANGES-PRINT-MOD-SCOPE-NOT: ModuleID = {{.+}} +; CHECK-CHANGES-PRINT-MOD-SCOPE: entry: + +; CHECK-CHANGES-FILTER-MULT-FUNC: *** IR Dump At Start: *** +; CHECK-CHANGES-FILTER-MULT-FUNC: *** IR Dump After InstSimplifyPass *** (function: g) +; CHECK-CHANGES-FILTER-MULT-FUNC-NOT: ModuleID = {{.+}} +; CHECK-CHANGES-FILTER-MULT-FUNC: entry: +; CHECK-CHANGES-FILTER-MULT-FUNC: *** IR Dump After InstSimplifyPass *** (function: f) +; CHECK-CHANGES-FILTER-MULT-FUNC-NOT: ModuleID = {{.+}} +; CHECK-CHANGES-FILTER-MULT-FUNC: entry: + +; CHECK-CHANGES-FILTER-PASSES: *** IR Dump After InstSimplifyPass (function: g) filtered out *** +; CHECK-CHANGES-FILTER-PASSES: *** IR Dump At Start: *** (function: g) +; CHECK-CHANGES-FILTER-PASSES: *** IR Dump After NoOpFunctionPass (function: g) omitted because no change *** +; CHECK-CHANGES-FILTER-PASSES: *** IR Dump After InstSimplifyPass (function: f) filtered out *** +; CHECK-CHANGES-FILTER-PASSES: *** IR Dump After NoOpFunctionPass (function: f) omitted because no change *** + +; CHECK-CHANGES-FILTER-MULT-PASSES: *** IR Dump At Start: *** (function: g) +; CHECK-CHANGES-FILTER-MULT-PASSES: *** IR Dump After InstSimplifyPass *** (function: g) +; CHECK-CHANGES-FILTER-MULT-PASSES-NOT: ModuleID = {{.+}} +; CHECK-CHANGES-FILTER-MULT-PASSES: entry: +; CHECK-CHANGES-FILTER-MULT-PASSES: *** IR Dump After NoOpFunctionPass (function: g) omitted because no change *** +; CHECK-CHANGES-FILTER-MULT-PASSES: *** IR Dump After InstSimplifyPass *** (function: f) +; CHECK-CHANGES-FILTER-MULT-PASSES-NOT: ModuleID = {{.+}} +; CHECK-CHANGES-FILTER-MULT-PASSES: entry: +; CHECK-CHANGES-FILTER-MULT-PASSES: *** IR Dump After NoOpFunctionPass (function: f) omitted because no change *** + +; CHECK-CHANGES-FILTER-FUNC-PASSES: *** IR Dump After InstSimplifyPass (function: g) filtered out *** +; CHECK-CHANGES-FILTER-FUNC-PASSES: *** IR Dump After NoOpFunctionPass (function: g) filtered out *** +; CHECK-CHANGES-FILTER-FUNC-PASSES: *** IR Dump At Start: *** (function: f) +; CHECK-CHANGES-FILTER-FUNC-PASSES: *** IR Dump After InstSimplifyPass *** (function: f) +; CHECK-CHANGES-FILTER-FUNC-PASSES-NOT: ModuleID = {{.+}} +; CHECK-CHANGES-FILTER-FUNC-PASSES: entry: +; CHECK-CHANGES-FILTER-FUNC-PASSES: *** IR Dump After NoOpFunctionPass (function: f) omitted because no change *** + +; CHECK-CHANGES-MULT-PASSES-FILTER-FUNC: *** IR Dump At Start: *** +; CHECK-CHANGES-MULT-PASSES-FILTER-FUNC: *** IR Dump After InstSimplifyPass (function: g) filtered out *** +; CHECK-CHANGES-MULT-PASSES-FILTER-FUNC: *** IR Dump After InstSimplifyPass (function: g) filtered out *** +; CHECK-CHANGES-MULT-PASSES-FILTER-FUNC: *** IR Dump After InstSimplifyPass *** (function: f) +; CHECK-CHANGES-MULT-PASSES-FILTER-FUNC-NOT: ModuleID = {{.+}} +; CHECK-CHANGES-MULT-PASSES-FILTER-FUNC: entry: +; CHECK-CHANGES-MULT-PASSES-FILTER-FUNC: *** IR Dump After InstSimplifyPass (function: f) omitted because no change ***