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