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