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