MLIR  22.0.0git
IRPrinting.cpp
Go to the documentation of this file.
1 //===- IRPrinting.cpp -----------------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "PassDetail.h"
10 #include "mlir/IR/SymbolTable.h"
11 #include "mlir/Pass/PassManager.h"
13 #include "llvm/ADT/STLExtras.h"
14 #include "llvm/ADT/StringExtras.h"
15 #include "llvm/Support/FileSystem.h"
16 #include "llvm/Support/FormatVariadic.h"
17 #include "llvm/Support/Path.h"
18 #include "llvm/Support/ToolOutputFile.h"
19 
20 using namespace mlir;
21 using namespace mlir::detail;
22 
23 namespace {
24 //===----------------------------------------------------------------------===//
25 // IRPrinter
26 //===----------------------------------------------------------------------===//
27 
28 class IRPrinterInstrumentation : public PassInstrumentation {
29 public:
30  IRPrinterInstrumentation(std::unique_ptr<PassManager::IRPrinterConfig> config)
31  : config(std::move(config)) {}
32 
33 private:
34  /// Instrumentation hooks.
35  void runBeforePass(Pass *pass, Operation *op) override;
36  void runAfterPass(Pass *pass, Operation *op) override;
37  void runAfterPassFailed(Pass *pass, Operation *op) override;
38 
39  /// Configuration to use.
40  std::unique_ptr<PassManager::IRPrinterConfig> config;
41 
42  /// The following is a set of fingerprints for operations that are currently
43  /// being operated on in a pass. This field is only used when the
44  /// configuration asked for change detection.
45  DenseMap<Pass *, OperationFingerPrint> beforePassFingerPrints;
46 };
47 } // namespace
48 
49 static void printIR(Operation *op, bool printModuleScope, raw_ostream &out,
50  OpPrintingFlags flags) {
51  // Otherwise, check to see if we are not printing at module scope.
52  if (!printModuleScope)
53  return op->print(out << " //----- //\n",
54  op->getBlock() ? flags.useLocalScope() : flags);
55 
56  // Otherwise, we are printing at module scope.
57  out << " ('" << op->getName() << "' operation";
58  if (auto symbolName =
60  out << ": @" << symbolName.getValue();
61  out << ") //----- //\n";
62 
63  // Find the top-level operation.
64  auto *topLevelOp = op;
65  while (auto *parentOp = topLevelOp->getParentOp())
66  topLevelOp = parentOp;
67  topLevelOp->print(out, flags);
68 }
69 
70 /// Instrumentation hooks.
71 void IRPrinterInstrumentation::runBeforePass(Pass *pass, Operation *op) {
72  if (isa<OpToOpPassAdaptor>(pass))
73  return;
74  // If the config asked to detect changes, record the current fingerprint.
75  if (config->shouldPrintAfterOnlyOnChange())
76  beforePassFingerPrints.try_emplace(pass, op);
77 
78  config->printBeforeIfEnabled(pass, op, [&](raw_ostream &out) {
79  out << "// -----// IR Dump Before " << pass->getName() << " ("
80  << pass->getArgument() << ")";
81  printIR(op, config->shouldPrintAtModuleScope(), out,
82  config->getOpPrintingFlags());
83  out << "\n\n";
84  });
85 }
86 
87 void IRPrinterInstrumentation::runAfterPass(Pass *pass, Operation *op) {
88  if (isa<OpToOpPassAdaptor>(pass))
89  return;
90 
91  // Check to see if we are only printing on failure.
92  if (config->shouldPrintAfterOnlyOnFailure())
93  return;
94 
95  // If the config asked to detect changes, compare the current fingerprint with
96  // the previous.
97  if (config->shouldPrintAfterOnlyOnChange()) {
98  auto fingerPrintIt = beforePassFingerPrints.find(pass);
99  assert(fingerPrintIt != beforePassFingerPrints.end() &&
100  "expected valid fingerprint");
101  // If the fingerprints are the same, we don't print the IR.
102  if (fingerPrintIt->second == OperationFingerPrint(op)) {
103  beforePassFingerPrints.erase(fingerPrintIt);
104  return;
105  }
106  beforePassFingerPrints.erase(fingerPrintIt);
107  }
108 
109  config->printAfterIfEnabled(pass, op, [&](raw_ostream &out) {
110  out << "// -----// IR Dump After " << pass->getName() << " ("
111  << pass->getArgument() << ")";
112  printIR(op, config->shouldPrintAtModuleScope(), out,
113  config->getOpPrintingFlags());
114  out << "\n\n";
115  });
116 }
117 
118 void IRPrinterInstrumentation::runAfterPassFailed(Pass *pass, Operation *op) {
119  if (isa<OpToOpPassAdaptor>(pass))
120  return;
121  if (config->shouldPrintAfterOnlyOnChange())
122  beforePassFingerPrints.erase(pass);
123 
124  config->printAfterIfEnabled(pass, op, [&](raw_ostream &out) {
125  out << formatv("// -----// IR Dump After {0} Failed ({1})", pass->getName(),
126  pass->getArgument());
127  printIR(op, config->shouldPrintAtModuleScope(), out,
128  config->getOpPrintingFlags());
129  out << "\n\n";
130  });
131 }
132 
133 //===----------------------------------------------------------------------===//
134 // IRPrinterConfig
135 //===----------------------------------------------------------------------===//
136 
137 /// Initialize the configuration.
139  bool printAfterOnlyOnChange,
140  bool printAfterOnlyOnFailure,
141  OpPrintingFlags opPrintingFlags)
142  : printModuleScope(printModuleScope),
143  printAfterOnlyOnChange(printAfterOnlyOnChange),
144  printAfterOnlyOnFailure(printAfterOnlyOnFailure),
145  opPrintingFlags(opPrintingFlags) {}
147 
148 /// A hook that may be overridden by a derived config that checks if the IR
149 /// of 'operation' should be dumped *before* the pass 'pass' has been
150 /// executed. If the IR should be dumped, 'printCallback' should be invoked
151 /// with the stream to dump into.
153  Pass *pass, Operation *operation, PrintCallbackFn printCallback) {
154  // By default, never print.
155 }
156 
157 /// A hook that may be overridden by a derived config that checks if the IR
158 /// of 'operation' should be dumped *after* the pass 'pass' has been
159 /// executed. If the IR should be dumped, 'printCallback' should be invoked
160 /// with the stream to dump into.
162  Pass *pass, Operation *operation, PrintCallbackFn printCallback) {
163  // By default, never print.
164 }
165 
166 //===----------------------------------------------------------------------===//
167 // PassManager
168 //===----------------------------------------------------------------------===//
169 
170 namespace {
171 /// Simple wrapper config that allows for the simpler interface defined above.
172 struct BasicIRPrinterConfig : public PassManager::IRPrinterConfig {
173  BasicIRPrinterConfig(
174  std::function<bool(Pass *, Operation *)> shouldPrintBeforePass,
175  std::function<bool(Pass *, Operation *)> shouldPrintAfterPass,
176  bool printModuleScope, bool printAfterOnlyOnChange,
177  bool printAfterOnlyOnFailure, OpPrintingFlags opPrintingFlags,
178  raw_ostream &out)
179  : IRPrinterConfig(printModuleScope, printAfterOnlyOnChange,
180  printAfterOnlyOnFailure, opPrintingFlags),
181  shouldPrintBeforePass(std::move(shouldPrintBeforePass)),
182  shouldPrintAfterPass(std::move(shouldPrintAfterPass)), out(out) {
183  assert((this->shouldPrintBeforePass || this->shouldPrintAfterPass) &&
184  "expected at least one valid filter function");
185  }
186 
187  void printBeforeIfEnabled(Pass *pass, Operation *operation,
188  PrintCallbackFn printCallback) final {
189  if (shouldPrintBeforePass && shouldPrintBeforePass(pass, operation))
190  printCallback(out);
191  }
192 
193  void printAfterIfEnabled(Pass *pass, Operation *operation,
194  PrintCallbackFn printCallback) final {
195  if (shouldPrintAfterPass && shouldPrintAfterPass(pass, operation))
196  printCallback(out);
197  }
198 
199  /// Filter functions for before and after pass execution.
200  std::function<bool(Pass *, Operation *)> shouldPrintBeforePass;
201  std::function<bool(Pass *, Operation *)> shouldPrintAfterPass;
202 
203  /// The stream to output to.
204  raw_ostream &out;
205 };
206 } // namespace
207 
208 /// Return pairs of (sanitized op name, symbol name) for `op` and all parent
209 /// operations. Op names are sanitized by replacing periods with underscores.
210 /// The pairs are returned in order of outer-most to inner-most (ancestors of
211 /// `op` first, `op` last). This information is used to construct the directory
212 /// tree for the `FileTreeIRPrinterConfig` below.
213 /// The counter for `op` will be incremented by this call.
214 static std::pair<SmallVector<std::pair<std::string, std::string>>, std::string>
215 getOpAndSymbolNames(Operation *op, StringRef passName,
218  SmallVector<unsigned> countPrefix;
219 
220  Operation *iter = op;
221  ++counters.try_emplace(op, -1).first->second;
222  while (iter) {
223  countPrefix.push_back(counters[iter]);
224  StringAttr symbolNameAttr =
225  iter->getAttrOfType<StringAttr>(SymbolTable::getSymbolAttrName());
226  std::string symbolName =
227  symbolNameAttr ? symbolNameAttr.str() : "no-symbol-name";
228  llvm::replace(symbolName, '/', '_');
229  llvm::replace(symbolName, '\\', '_');
230 
231  std::string opName =
232  llvm::join(llvm::split(iter->getName().getStringRef().str(), '.'), "_");
233  pathElements.emplace_back(std::move(opName), std::move(symbolName));
234  iter = iter->getParentOp();
235  }
236  // Return in the order of top level (module) down to `op`.
237  std::reverse(countPrefix.begin(), countPrefix.end());
238  std::reverse(pathElements.begin(), pathElements.end());
239 
240  std::string passFileName = llvm::formatv(
241  "{0:$[_]}_{1}.mlir",
242  llvm::make_range(countPrefix.begin(), countPrefix.end()), passName);
243 
244  return {pathElements, passFileName};
245 }
246 
247 static LogicalResult createDirectoryOrPrintErr(llvm::StringRef dirPath) {
248  if (std::error_code ec =
249  llvm::sys::fs::create_directory(dirPath, /*IgnoreExisting=*/true)) {
250  llvm::errs() << "Error while creating directory " << dirPath << ": "
251  << ec.message() << "\n";
252  return failure();
253  }
254  return success();
255 }
256 
257 /// Creates directories (if required) and opens an output file for the
258 /// FileTreeIRPrinterConfig.
259 static std::unique_ptr<llvm::ToolOutputFile>
260 createTreePrinterOutputPath(Operation *op, llvm::StringRef passArgument,
261  llvm::StringRef rootDir,
263  // Create the path. We will create a tree rooted at the given 'rootDir'
264  // directory. The root directory will contain folders with the names of
265  // modules. Sub-directories within those folders mirror the nesting
266  // structure of the pass manager, using symbol names for directory names.
267  auto [opAndSymbolNames, fileName] =
268  getOpAndSymbolNames(op, passArgument, counters);
269 
270  // Create all the directories, starting at the root. Abort early if we fail to
271  // create any directory.
272  llvm::SmallString<128> path(rootDir);
274  return nullptr;
275 
276  for (const auto &[opName, symbolName] : opAndSymbolNames) {
277  llvm::sys::path::append(path, opName + "_" + symbolName);
279  return nullptr;
280  }
281 
282  // Open output file.
283  llvm::sys::path::append(path, fileName);
284  std::string error;
285  std::unique_ptr<llvm::ToolOutputFile> file = openOutputFile(path, &error);
286  if (!file) {
287  llvm::errs() << "Error opening output file " << path << ": " << error
288  << "\n";
289  return nullptr;
290  }
291  return file;
292 }
293 
294 namespace {
295 /// A configuration that prints the IR before/after each pass to a set of files
296 /// in the specified directory. The files are organized into subdirectories that
297 /// mirror the nesting structure of the IR.
298 struct FileTreeIRPrinterConfig : public PassManager::IRPrinterConfig {
299  FileTreeIRPrinterConfig(
300  std::function<bool(Pass *, Operation *)> shouldPrintBeforePass,
301  std::function<bool(Pass *, Operation *)> shouldPrintAfterPass,
302  bool printModuleScope, bool printAfterOnlyOnChange,
303  bool printAfterOnlyOnFailure, OpPrintingFlags opPrintingFlags,
304  llvm::StringRef treeDir)
305  : IRPrinterConfig(printModuleScope, printAfterOnlyOnChange,
306  printAfterOnlyOnFailure, opPrintingFlags),
307  shouldPrintBeforePass(std::move(shouldPrintBeforePass)),
308  shouldPrintAfterPass(std::move(shouldPrintAfterPass)),
309  treeDir(treeDir) {
310  assert((this->shouldPrintBeforePass || this->shouldPrintAfterPass) &&
311  "expected at least one valid filter function");
312  }
313 
314  void printBeforeIfEnabled(Pass *pass, Operation *operation,
315  PrintCallbackFn printCallback) final {
316  if (!shouldPrintBeforePass || !shouldPrintBeforePass(pass, operation))
317  return;
318  std::unique_ptr<llvm::ToolOutputFile> file = createTreePrinterOutputPath(
319  operation, pass->getArgument(), treeDir, counters);
320  if (!file)
321  return;
322  printCallback(file->os());
323  file->keep();
324  }
325 
326  void printAfterIfEnabled(Pass *pass, Operation *operation,
327  PrintCallbackFn printCallback) final {
328  if (!shouldPrintAfterPass || !shouldPrintAfterPass(pass, operation))
329  return;
330  std::unique_ptr<llvm::ToolOutputFile> file = createTreePrinterOutputPath(
331  operation, pass->getArgument(), treeDir, counters);
332  if (!file)
333  return;
334  printCallback(file->os());
335  file->keep();
336  }
337 
338  /// Filter functions for before and after pass execution.
339  std::function<bool(Pass *, Operation *)> shouldPrintBeforePass;
340  std::function<bool(Pass *, Operation *)> shouldPrintAfterPass;
341 
342  /// Directory that should be used as the root of the file tree.
343  std::string treeDir;
344 
345  /// Counters used for labeling the prefix. Every op which could be targeted by
346  /// a pass gets its own counter.
348 };
349 
350 } // namespace
351 
352 /// Add an instrumentation to print the IR before and after pass execution,
353 /// using the provided configuration.
354 void PassManager::enableIRPrinting(std::unique_ptr<IRPrinterConfig> config) {
355  if (config->shouldPrintAtModuleScope() &&
356  getContext()->isMultithreadingEnabled())
357  llvm::report_fatal_error("IR printing can't be setup on a pass-manager "
358  "without disabling multi-threading first.");
360  std::make_unique<IRPrinterInstrumentation>(std::move(config)));
361 }
362 
363 /// Add an instrumentation to print the IR before and after pass execution.
365  std::function<bool(Pass *, Operation *)> shouldPrintBeforePass,
366  std::function<bool(Pass *, Operation *)> shouldPrintAfterPass,
367  bool printModuleScope, bool printAfterOnlyOnChange,
368  bool printAfterOnlyOnFailure, raw_ostream &out,
369  OpPrintingFlags opPrintingFlags) {
370  enableIRPrinting(std::make_unique<BasicIRPrinterConfig>(
371  std::move(shouldPrintBeforePass), std::move(shouldPrintAfterPass),
372  printModuleScope, printAfterOnlyOnChange, printAfterOnlyOnFailure,
373  opPrintingFlags, out));
374 }
375 
376 /// Add an instrumentation to print the IR before and after pass execution.
378  std::function<bool(Pass *, Operation *)> shouldPrintBeforePass,
379  std::function<bool(Pass *, Operation *)> shouldPrintAfterPass,
380  bool printModuleScope, bool printAfterOnlyOnChange,
381  bool printAfterOnlyOnFailure, StringRef printTreeDir,
382  OpPrintingFlags opPrintingFlags) {
383  enableIRPrinting(std::make_unique<FileTreeIRPrinterConfig>(
384  std::move(shouldPrintBeforePass), std::move(shouldPrintAfterPass),
385  printModuleScope, printAfterOnlyOnChange, printAfterOnlyOnFailure,
386  opPrintingFlags, printTreeDir));
387 }
static std::pair< SmallVector< std::pair< std::string, std::string > >, std::string > getOpAndSymbolNames(Operation *op, StringRef passName, llvm::DenseMap< Operation *, unsigned > &counters)
Return pairs of (sanitized op name, symbol name) for op and all parent operations.
Definition: IRPrinting.cpp:215
static std::unique_ptr< llvm::ToolOutputFile > createTreePrinterOutputPath(Operation *op, llvm::StringRef passArgument, llvm::StringRef rootDir, llvm::DenseMap< Operation *, unsigned > &counters)
Creates directories (if required) and opens an output file for the FileTreeIRPrinterConfig.
Definition: IRPrinting.cpp:260
static void printIR(Operation *op, bool printModuleScope, raw_ostream &out, OpPrintingFlags flags)
Definition: IRPrinting.cpp:49
static LogicalResult createDirectoryOrPrintErr(llvm::StringRef dirPath)
Definition: IRPrinting.cpp:247
Set of flags used to control the behavior of the various IR print methods (e.g.
OpPrintingFlags & useLocalScope(bool enable=true)
Use local scope when printing the operation.
Definition: AsmPrinter.cpp:296
A unique fingerprint for a specific operation, and all of it's internal operations (if includeNested ...
Operation is the basic unit of execution within MLIR.
Definition: Operation.h:88
AttrClass getAttrOfType(StringAttr name)
Definition: Operation.h:550
Block * getBlock()
Returns the operation block that contains this operation.
Definition: Operation.h:213
OperationName getName()
The name of an operation is the key identifier for it.
Definition: Operation.h:119
void print(raw_ostream &os, const OpPrintingFlags &flags={})
PassInstrumentation provides several entry points into the pass manager infrastructure.
A configuration struct provided to the IR printer instrumentation.
Definition: PassManager.h:289
IRPrinterConfig(bool printModuleScope=false, bool printAfterOnlyOnChange=false, bool printAfterOnlyOnFailure=false, OpPrintingFlags opPrintingFlags=OpPrintingFlags())
Initialize the configuration.
Definition: IRPrinting.cpp:138
virtual void printBeforeIfEnabled(Pass *pass, Operation *operation, PrintCallbackFn printCallback)
A hook that may be overridden by a derived config that checks if the IR of 'operation' should be dump...
Definition: IRPrinting.cpp:152
virtual void printAfterIfEnabled(Pass *pass, Operation *operation, PrintCallbackFn printCallback)
A hook that may be overridden by a derived config that checks if the IR of 'operation' should be dump...
Definition: IRPrinting.cpp:161
MLIRContext * getContext() const
Return an instance of the context.
Definition: PassManager.h:257
void enableIRPrinting(std::unique_ptr< IRPrinterConfig > config)
Add an instrumentation to print the IR before and after pass execution, using the provided configurat...
Definition: IRPrinting.cpp:354
void enableIRPrintingToFileTree(std::function< bool(Pass *, Operation *)> shouldPrintBeforePass=[](Pass *, Operation *) { return true;}, std::function< bool(Pass *, Operation *)> shouldPrintAfterPass=[](Pass *, Operation *) { return true;}, bool printModuleScope=true, bool printAfterOnlyOnChange=true, bool printAfterOnlyOnFailure=false, llvm::StringRef printTreeDir=".pass_manager_output", OpPrintingFlags opPrintingFlags=OpPrintingFlags())
Similar to enableIRPrinting above, except that instead of printing the IR to a single output stream,...
Definition: IRPrinting.cpp:377
void addInstrumentation(std::unique_ptr< PassInstrumentation > pi)
Add the provided instrumentation to the pass manager.
Definition: Pass.cpp:925
The abstract base pass class.
Definition: Pass.h:51
virtual StringRef getName() const =0
Returns the derived pass name.
virtual StringRef getArgument() const
Return the command line argument used when registering this pass.
Definition: Pass.h:75
static StringRef getSymbolAttrName()
Return the name of the attribute used for symbol names.
Definition: SymbolTable.h:76
AttrTypeReplacer.
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition: Remarks.h:491
Include the generated interface declarations.
const FrozenRewritePatternSet GreedyRewriteConfig config
std::unique_ptr< llvm::ToolOutputFile > openOutputFile(llvm::StringRef outputFilename, std::string *errorMessage=nullptr)
Open the file specified by its name for writing.