//===- IRPrinting.cpp -----------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "PassDetail.h" #include "mlir/IR/SymbolTable.h" #include "mlir/Pass/PassManager.h" #include "llvm/Support/Format.h" #include "llvm/Support/FormatVariadic.h" using namespace mlir; using namespace mlir::detail; namespace { //===----------------------------------------------------------------------===// // IRPrinter //===----------------------------------------------------------------------===// class IRPrinterInstrumentation : public PassInstrumentation { public: IRPrinterInstrumentation(std::unique_ptr config) : config(std::move(config)) {} private: /// Instrumentation hooks. void runBeforePass(Pass *pass, Operation *op) override; void runAfterPass(Pass *pass, Operation *op) override; void runAfterPassFailed(Pass *pass, Operation *op) override; /// Configuration to use. std::unique_ptr config; /// The following is a set of fingerprints for operations that are currently /// being operated on in a pass. This field is only used when the /// configuration asked for change detection. DenseMap beforePassFingerPrints; }; } // namespace static void printIR(Operation *op, bool printModuleScope, raw_ostream &out, OpPrintingFlags flags) { // Otherwise, check to see if we are not printing at module scope. if (!printModuleScope) return op->print(out << " //----- //\n", op->getBlock() ? flags.useLocalScope() : flags); // Otherwise, we are printing at module scope. out << " ('" << op->getName() << "' operation"; if (auto symbolName = op->getAttrOfType(SymbolTable::getSymbolAttrName())) out << ": @" << symbolName.getValue(); out << ") //----- //\n"; // Find the top-level operation. auto *topLevelOp = op; while (auto *parentOp = topLevelOp->getParentOp()) topLevelOp = parentOp; topLevelOp->print(out, flags); } /// Instrumentation hooks. void IRPrinterInstrumentation::runBeforePass(Pass *pass, Operation *op) { if (isa(pass)) return; // If the config asked to detect changes, record the current fingerprint. if (config->shouldPrintAfterOnlyOnChange()) beforePassFingerPrints.try_emplace(pass, op); config->printBeforeIfEnabled(pass, op, [&](raw_ostream &out) { out << "// -----// IR Dump Before " << pass->getName() << " (" << pass->getArgument() << ")"; printIR(op, config->shouldPrintAtModuleScope(), out, config->getOpPrintingFlags()); out << "\n\n"; }); } void IRPrinterInstrumentation::runAfterPass(Pass *pass, Operation *op) { if (isa(pass)) return; // Check to see if we are only printing on failure. if (config->shouldPrintAfterOnlyOnFailure()) return; // If the config asked to detect changes, compare the current fingerprint with // the previous. if (config->shouldPrintAfterOnlyOnChange()) { auto fingerPrintIt = beforePassFingerPrints.find(pass); assert(fingerPrintIt != beforePassFingerPrints.end() && "expected valid fingerprint"); // If the fingerprints are the same, we don't print the IR. if (fingerPrintIt->second == OperationFingerPrint(op)) { beforePassFingerPrints.erase(fingerPrintIt); return; } beforePassFingerPrints.erase(fingerPrintIt); } config->printAfterIfEnabled(pass, op, [&](raw_ostream &out) { out << "// -----// IR Dump After " << pass->getName() << " (" << pass->getArgument() << ")"; printIR(op, config->shouldPrintAtModuleScope(), out, config->getOpPrintingFlags()); out << "\n\n"; }); } void IRPrinterInstrumentation::runAfterPassFailed(Pass *pass, Operation *op) { if (isa(pass)) return; if (config->shouldPrintAfterOnlyOnChange()) beforePassFingerPrints.erase(pass); config->printAfterIfEnabled(pass, op, [&](raw_ostream &out) { out << formatv("// -----// IR Dump After {0} Failed ({1})", pass->getName(), pass->getArgument()); printIR(op, config->shouldPrintAtModuleScope(), out, config->getOpPrintingFlags()); out << "\n\n"; }); } //===----------------------------------------------------------------------===// // IRPrinterConfig //===----------------------------------------------------------------------===// /// Initialize the configuration. PassManager::IRPrinterConfig::IRPrinterConfig(bool printModuleScope, bool printAfterOnlyOnChange, bool printAfterOnlyOnFailure, OpPrintingFlags opPrintingFlags) : printModuleScope(printModuleScope), printAfterOnlyOnChange(printAfterOnlyOnChange), printAfterOnlyOnFailure(printAfterOnlyOnFailure), opPrintingFlags(opPrintingFlags) {} PassManager::IRPrinterConfig::~IRPrinterConfig() = default; /// A hook that may be overridden by a derived config that checks if the IR /// of 'operation' should be dumped *before* the pass 'pass' has been /// executed. If the IR should be dumped, 'printCallback' should be invoked /// with the stream to dump into. void PassManager::IRPrinterConfig::printBeforeIfEnabled( Pass *pass, Operation *operation, PrintCallbackFn printCallback) { // By default, never print. } /// A hook that may be overridden by a derived config that checks if the IR /// of 'operation' should be dumped *after* the pass 'pass' has been /// executed. If the IR should be dumped, 'printCallback' should be invoked /// with the stream to dump into. void PassManager::IRPrinterConfig::printAfterIfEnabled( Pass *pass, Operation *operation, PrintCallbackFn printCallback) { // By default, never print. } //===----------------------------------------------------------------------===// // PassManager //===----------------------------------------------------------------------===// namespace { /// Simple wrapper config that allows for the simpler interface defined above. struct BasicIRPrinterConfig : public PassManager::IRPrinterConfig { BasicIRPrinterConfig( std::function shouldPrintBeforePass, std::function shouldPrintAfterPass, bool printModuleScope, bool printAfterOnlyOnChange, bool printAfterOnlyOnFailure, OpPrintingFlags opPrintingFlags, raw_ostream &out) : IRPrinterConfig(printModuleScope, printAfterOnlyOnChange, printAfterOnlyOnFailure, opPrintingFlags), shouldPrintBeforePass(std::move(shouldPrintBeforePass)), shouldPrintAfterPass(std::move(shouldPrintAfterPass)), out(out) { assert((this->shouldPrintBeforePass || this->shouldPrintAfterPass) && "expected at least one valid filter function"); } void printBeforeIfEnabled(Pass *pass, Operation *operation, PrintCallbackFn printCallback) final { if (shouldPrintBeforePass && shouldPrintBeforePass(pass, operation)) printCallback(out); } void printAfterIfEnabled(Pass *pass, Operation *operation, PrintCallbackFn printCallback) final { if (shouldPrintAfterPass && shouldPrintAfterPass(pass, operation)) printCallback(out); } /// Filter functions for before and after pass execution. std::function shouldPrintBeforePass; std::function shouldPrintAfterPass; /// The stream to output to. raw_ostream &out; }; } // namespace /// Add an instrumentation to print the IR before and after pass execution, /// using the provided configuration. void PassManager::enableIRPrinting(std::unique_ptr config) { if (config->shouldPrintAtModuleScope() && getContext()->isMultithreadingEnabled()) llvm::report_fatal_error("IR printing can't be setup on a pass-manager " "without disabling multi-threading first."); addInstrumentation( std::make_unique(std::move(config))); } /// Add an instrumentation to print the IR before and after pass execution. void PassManager::enableIRPrinting( std::function shouldPrintBeforePass, std::function shouldPrintAfterPass, bool printModuleScope, bool printAfterOnlyOnChange, bool printAfterOnlyOnFailure, raw_ostream &out, OpPrintingFlags opPrintingFlags) { enableIRPrinting(std::make_unique( std::move(shouldPrintBeforePass), std::move(shouldPrintAfterPass), printModuleScope, printAfterOnlyOnChange, printAfterOnlyOnFailure, opPrintingFlags, out)); }