1 # Diagnostic Infrastructure
5 This document presents an introduction to using and interfacing with MLIR's
6 diagnostics infrastructure.
8 See [MLIR specification](LangRef.md) for more information about MLIR, the
9 structure of the IR, operations, etc.
13 Source location information is extremely important for any compiler, because it
14 provides a baseline for debuggability and error-reporting. The
15 [builtin dialect](Dialects/Builtin.md) provides several different location
16 attributes types depending on the situational need.
20 The `DiagnosticEngine` acts as the main interface for diagnostics in MLIR. It
21 manages the registration of diagnostic handlers, as well as the core API for
22 diagnostic emission. Handlers generally take the form of
23 `LogicalResult(Diagnostic &)`. If the result is `success`, it signals that the
24 diagnostic has been fully processed and consumed. If `failure`, it signals that
25 the diagnostic should be propagated to any previously registered handlers. It
26 can be interfaced with via an `MLIRContext` instance.
29 DiagnosticEngine& engine = ctx->getDiagEngine();
31 /// Handle the reported diagnostic.
32 // Return success to signal that the diagnostic has either been fully processed,
33 // or failure if the diagnostic should be propagated to the previous handlers.
34 DiagnosticEngine::HandlerID id = engine.registerHandler(
35 [](Diagnostic &diag) -> LogicalResult {
36 bool should_propagate_diagnostic = ...;
37 return failure(should_propagate_diagnostic);
41 // We can also elide the return value completely, in which the engine assumes
42 // that all diagnostics are consumed(i.e. a success() result).
43 DiagnosticEngine::HandlerID id = engine.registerHandler([](Diagnostic &diag) {
47 // Unregister this handler when we are done.
48 engine.eraseHandler(id);
51 ### Constructing a Diagnostic
53 As stated above, the `DiagnosticEngine` holds the core API for diagnostic
54 emission. A new diagnostic can be emitted with the engine via `emit`. This
55 method returns an [InFlightDiagnostic](#inflight-diagnostic) that can be
59 InFlightDiagnostic emit(Location loc, DiagnosticSeverity severity);
62 Using the `DiagnosticEngine`, though, is generally not the preferred way to emit
63 diagnostics in MLIR. [`operation`](LangRef.md/#operations) provides utility
64 methods for emitting diagnostics:
67 // `emit` methods available in the mlir namespace.
68 InFlightDiagnostic emitError/Remark/Warning(Location);
70 // These methods use the location attached to the operation.
71 InFlightDiagnostic Operation::emitError/Remark/Warning();
73 // This method creates a diagnostic prefixed with "'op-name' op ".
74 InFlightDiagnostic Operation::emitOpError();
79 A `Diagnostic` in MLIR contains all of the necessary information for reporting a
80 message to the user. A `Diagnostic` essentially boils down to four main
83 * [Source Location](#source-locations)
85 - Error, Note, Remark, Warning
86 * Diagnostic Arguments
87 - The diagnostic arguments are used when constructing the output message.
89 - Some additional information attached that can be used to identify
90 this diagnostic other than source location and severity level
91 (e.g. for diagnostic handlers to do some filtering).
92 Metadata is not part of the output message.
94 ### Appending arguments
96 One a diagnostic has been constructed, the user can start composing it. The
97 output message of a diagnostic is composed of a set of diagnostic arguments that
98 have been attached to it. New arguments can be attached to a diagnostic in a few
102 // A few interesting things to use when composing a diagnostic.
105 SmallVector<int> fooInts;
107 // Diagnostics can be composed via the streaming operators.
108 op->emitError() << "Compose an interesting error: " << fooAttr << ", " << fooType
109 << ", (" << fooInts << ')';
111 // This could generate something like (FuncAttr:@foo, IntegerType:i32, {0,1,2}):
112 "Compose an interesting error: @foo, i32, (0, 1, 2)"
115 Operations attached to a diagnostic will be printed in generic form if the
116 severity level is `Error`, otherwise custom operation printers will be used.
118 // `anotherOp` will be printed in generic form,
119 // e.g. %3 = "arith.addf"(%arg4, %2) : (f32, f32) -> f32
120 op->emitError() << anotherOp;
122 // `anotherOp` will be printed using the custom printer,
123 // e.g. %3 = arith.addf %arg4, %2 : f32
124 op->emitRemark() << anotherOp;
127 To make a custom type compatible with Diagnostics, one must implement the
128 following friend function.
131 friend mlir::Diagnostic &operator<<(
132 mlir::Diagnostic &diagnostic, const MyType &foo);
137 Unlike many other compiler frameworks, notes in MLIR cannot be emitted directly.
138 They must be explicitly attached to another diagnostic non-note diagnostic. When
139 emitting a diagnostic, notes can be directly attached via `attachNote`. When
140 attaching a note, if the user does not provide an explicit source location the
141 note will inherit the location of the parent diagnostic.
144 // Emit a note with an explicit source location.
145 op->emitError("...").attachNote(noteLoc) << "...";
147 // Emit a note that inherits the parent location.
148 op->emitError("...").attachNote() << "...";
151 ### Managing Metadata
152 Metadata is a mutable vector of DiagnosticArguments.
153 It can be accessed and modified as a vector.
156 ## InFlight Diagnostic
158 Now that [Diagnostics](#diagnostic) have been explained, we introduce the
159 `InFlightDiagnostic`, an RAII wrapper around a diagnostic that is set to be
160 reported. This allows for modifying a diagnostic while it is still in flight. If
161 it is not reported directly by the user it will automatically report when
166 InFlightDiagnostic diag = op->emitError() << "...";
167 } // The diagnostic is automatically reported here.
170 ## Diagnostic Configuration Options
172 Several options are provided to help control and enhance the behavior of
173 diagnostics. These options can be configured via the MLIRContext, and registered
174 to the command line with the `registerMLIRContextCLOptions` method. These
175 options are listed below:
177 ### Print Operation On Diagnostic
179 Command Line Flag: `-mlir-print-op-on-diagnostic`
181 When a diagnostic is emitted on an operation, via `Operation::emitError/...`,
182 the textual form of that operation is printed and attached as a note to the
183 diagnostic. This option is useful for understanding the current form of an
184 operation that may be invalid, especially when debugging verifier failures. An
185 example output is shown below:
188 test.mlir:3:3: error: 'module_terminator' op expects parent op 'builtin.module'
189 "module_terminator"() : () -> ()
191 test.mlir:3:3: note: see current operation: "module_terminator"() : () -> ()
192 "module_terminator"() : () -> ()
196 ### Print StackTrace On Diagnostic
198 Command Line Flag: `-mlir-print-stacktrace-on-diagnostic`
200 When a diagnostic is emitted, attach the current stack trace as a note to the
201 diagnostic. This option is useful for understanding which part of the compiler
202 generated certain diagnostics. An example output is shown below:
205 test.mlir:3:3: error: 'module_terminator' op expects parent op 'builtin.module'
206 "module_terminator"() : () -> ()
208 test.mlir:3:3: note: diagnostic emitted with trace:
209 #0 0x000055dd40543805 llvm::sys::PrintStackTrace(llvm::raw_ostream&) llvm/lib/Support/Unix/Signals.inc:553:11
210 #1 0x000055dd3f8ac162 emitDiag(mlir::Location, mlir::DiagnosticSeverity, llvm::Twine const&) /lib/IR/Diagnostics.cpp:292:7
211 #2 0x000055dd3f8abe8e mlir::emitError(mlir::Location, llvm::Twine const&) /lib/IR/Diagnostics.cpp:304:10
212 #3 0x000055dd3f998e87 mlir::Operation::emitError(llvm::Twine const&) /lib/IR/Operation.cpp:324:29
213 #4 0x000055dd3f99d21c mlir::Operation::emitOpError(llvm::Twine const&) /lib/IR/Operation.cpp:652:10
214 #5 0x000055dd3f96b01c mlir::OpTrait::HasParent<mlir::ModuleOp>::Impl<mlir::ModuleTerminatorOp>::verifyTrait(mlir::Operation*) /mlir/IR/OpDefinition.h:897:18
215 #6 0x000055dd3f96ab38 mlir::Op<mlir::ModuleTerminatorOp, mlir::OpTrait::ZeroOperands, mlir::OpTrait::ZeroResults, mlir::OpTrait::HasParent<mlir::ModuleOp>::Impl, mlir::OpTrait::IsTerminator>::BaseVerifier<mlir::OpTrait::HasParent<mlir::ModuleOp>::Impl<mlir::ModuleTerminatorOp>, mlir::OpTrait::IsTerminator<mlir::ModuleTerminatorOp> >::verifyTrait(mlir::Operation*) /mlir/IR/OpDefinition.h:1052:29
217 "module_terminator"() : () -> ()
221 ## Common Diagnostic Handlers
223 To interface with the diagnostics infrastructure, users will need to register a
224 diagnostic handler with the [`DiagnosticEngine`](#diagnostic-engine).
225 Recognizing the many users will want the same handler functionality, MLIR
226 provides several common diagnostic handlers for immediate use.
228 ### Scoped Diagnostic Handler
230 This diagnostic handler is a simple RAII class that registers and unregisters a
231 given diagnostic handler. This class can be either be used directly, or in
232 conjunction with a derived diagnostic handler.
235 // Construct the handler directly.
237 ScopedDiagnosticHandler scopedHandler(&context, [](Diagnostic &diag) {
241 // Use this handler in conjunction with another.
242 class MyDerivedHandler : public ScopedDiagnosticHandler {
243 MyDerivedHandler(MLIRContext *ctx) : ScopedDiagnosticHandler(ctx) {
244 // Set the handler that should be RAII managed.
245 setHandler([&](Diagnostic diag) {
252 ### SourceMgr Diagnostic Handler
254 This diagnostic handler is a wrapper around an llvm::SourceMgr instance. It
255 provides support for displaying diagnostic messages inline with a line of a
256 respective source file. This handler will also automatically load newly seen
257 source files into the SourceMgr when attempting to display the source line of a
258 diagnostic. Example usage of this handler can be seen in the `mlir-opt` tool.
263 /tmp/test.mlir:6:24: error: expected non-function type
264 func.func @foo() -> (index, ind) {
268 To use this handler in your tool, add the following:
273 SourceMgrDiagnosticHandler sourceMgrHandler(sourceMgr, &context);
276 #### Filtering Locations
278 In some situations, a diagnostic may be emitted with a callsite location in a
279 very deep call stack in which many frames are unrelated to the user source code.
280 These situations often arise when the user source code is intertwined with that
281 of a large framework or library. The context of the diagnostic in these cases is
282 often obfuscated by the unrelated framework source locations. To help alleviate
283 this obfuscation, the `SourceMgrDiagnosticHandler` provides support for
284 filtering which locations are shown to the user. To enable filtering, a user
285 must simply provide a filter function to the `SourceMgrDiagnosticHandler` on
286 construction that indicates which locations should be shown. A quick example is
290 // Here we define the functor that controls which locations are shown to the
291 // user. This functor should return true when a location should be shown, and
292 // false otherwise. When filtering a container location, such as a NameLoc, this
293 // function should not recurse into the child location. Recursion into nested
294 // location is performed as necessary by the caller.
295 auto shouldShowFn = [](Location loc) -> bool {
296 FileLineColLoc fileLoc = loc.dyn_cast<FileLineColLoc>();
298 // We don't perform any filtering on non-file locations.
299 // Reminder: The caller will recurse into any necessary child locations.
303 // Don't show file locations that contain our framework code.
304 return !fileLoc.getFilename().strref().contains("my/framework/source/");
309 SourceMgrDiagnosticHandler sourceMgrHandler(sourceMgr, &context, shouldShowFn);
312 Note: In the case where all locations are filtered out, the first location in
313 the stack will still be shown.
315 ### SourceMgr Diagnostic Verifier Handler
317 This handler is a wrapper around a llvm::SourceMgr that is used to verify that
318 certain diagnostics have been emitted to the context. To use this handler,
319 annotate your source file with expected diagnostics in the form of:
321 * `expected-(error|note|remark|warning)(-re)? {{ message }}`
323 The provided `message` is a string expected to be contained within the generated
324 diagnostic. The `-re` suffix may be used to enable regex matching within the
325 `message`. When present, the `message` may define regex match sequences within
326 `{{` `}}` blocks. The regular expression matcher supports Extended POSIX regular
327 expressions (ERE). A few examples are shown below:
330 // Expect an error on the same line.
331 func.func @bad_branch() {
332 cf.br ^missing // expected-error {{reference to an undefined block}}
335 // Expect an error on an adjacent line.
336 func.func @foo(%a : f32) {
337 // expected-error@+1 {{unknown comparison predicate "foo"}}
338 %result = arith.cmpf "foo", %a, %a : f32
342 // Expect an error on the next line that does not contain a designator.
343 // expected-remark@below {{remark on function below}}
344 // expected-remark@below {{another remark on function below}}
345 func.func @bar(%a : f32)
347 // Expect an error on the previous line that does not contain a designator.
348 func.func @baz(%a : f32)
349 // expected-remark@above {{remark on function above}}
350 // expected-remark@above {{another remark on function above}}
352 // Expect an error mentioning the parent function, but use regex to avoid
353 // hardcoding the name.
354 func.func @foo() -> i32 {
355 // expected-error-re@+1 {{'func.return' op has 0 operands, but enclosing function (@{{.*}}) returns 1}}
360 The handler will report an error if any unexpected diagnostics were seen, or if
361 any expected diagnostics weren't.
366 /tmp/test.mlir:6:24: error: unexpected error: expected non-function type
367 func.func @foo() -> (index, ind) {
370 /tmp/test.mlir:15:4: error: expected remark "expected some remark" was not produced
371 // expected-remark {{expected some remark}}
372 ^~~~~~~~~~~~~~~~~~~~~~~~~~
375 Similarly to the [SourceMgr Diagnostic Handler](#sourcemgr-diagnostic-handler),
376 this handler can be added to any tool via the following:
381 SourceMgrDiagnosticVerifierHandler sourceMgrHandler(sourceMgr, &context);
384 ### Parallel Diagnostic Handler
386 MLIR is designed from the ground up to be multi-threaded. One important to thing
387 to keep in mind when multi-threading is determinism. This means that the
388 behavior seen when operating on multiple threads is the same as when operating
389 on a single thread. For diagnostics, this means that the ordering of the
390 diagnostics is the same regardless of the amount of threads being operated on.
391 The ParallelDiagnosticHandler is introduced to solve this problem.
393 After creating a handler of this type, the only remaining step is to ensure that
394 each thread that will be emitting diagnostics to the handler sets a respective
395 'orderID'. The orderID corresponds to the order in which diagnostics would be
396 emitted when executing synchronously. For example, if we were processing a list
397 of operations [a, b, c] on a single-thread. Diagnostics emitted while processing
398 operation 'a' would be emitted before those for 'b' or 'c'. This corresponds 1-1
399 with the 'orderID'. The thread that is processing 'a' should set the orderID to
400 '0'; the thread processing 'b' should set it to '1'; and so on and so forth.
401 This provides a way for the handler to deterministically order the diagnostics
402 that it receives given the thread that it is receiving on.
404 A simple example is shown below:
407 MLIRContext *context = ...;
408 ParallelDiagnosticHandler handler(context);
410 // Process a list of operations in parallel.
411 std::vector<Operation *> opsToProcess = ...;
412 llvm::parallelFor(0, opsToProcess.size(), [&](size_t i) {
413 // Notify the handler that we are processing the i'th operation.
414 handler.setOrderIDForThread(i);
415 auto *op = opsToProcess[i];
418 // Notify the handler that we are finished processing diagnostics on this
420 handler.eraseOrderIDForThread();