blob: b44ec2cc3f055f45c64124e24d9d2911b606c6fc [file] [log] [blame] [view] [edit]
# Remark Infrastructure
Remarks are **structured, human- and machine-readable notes** emitted by the
compiler to communicate:
- What transformations were applied
- What optimizations were missed
- Why certain decisions were made
The **`RemarkEngine`** collects remarks during compilation and routes them to a
pluggable **streamer**. By default, MLIR integrates with LLVM's
[`llvm::remarks`](https://llvm.org/docs/Remarks.html) infrastructure, enabling
you to:
- Stream remarks as passes run
- Serialize to **YAML** or **LLVM Bitstream**
***
## Overview
- **Opt-in** – Disabled by default; zero overhead unless enabled.
- **Per-context** – Configured on `MLIRContext`.
- **Formats** – LLVM Remark engine (YAML / Bitstream) or custom streamers.
- **Kinds** – `Passed`, `Missed`, `Failure`, `Analysis`.
- **API** – Lightweight streaming interface using `<<` (like MLIR diagnostics).
***
## Architecture
The remark system consists of two main components:
### RemarkEngine
Owned by `MLIRContext`, the engine:
- Receives finalized `InFlightRemark` objects
- Optionally mirrors remarks to the `DiagnosticEngine`
- Dispatches to the installed streamer
### MLIRRemarkStreamerBase
An abstract backend interface with a single hook:
```c++
virtual void streamOptimizationRemark(const Remark &remark) = 0;
```
The default implementation, **`MLIRLLVMRemarkStreamer`**, adapts `mlir::Remark`
to LLVM's remark format and writes YAML or Bitstream via
`llvm::remarks::RemarkStreamer`.
**Ownership chain:** `MLIRContext` `RemarkEngine` `MLIRRemarkStreamerBase`
***
## Remark Categories
MLIR provides four built-in categories:
### Passed
An optimization or transformation succeeded.
```
[Passed] RemarkName | Category:Vectorizer:myPass1 | Function=foo | Remark="vectorized loop", tripCount=128
```
### Missed
An optimization didn't apply and produces ideally an actionable feedback.
```
[Missed] | Category:Unroll | Function=foo | Reason="tripCount=4 < threshold=256", Suggestion="increase unroll to 128"
```
### Failure
An optimization was attempted but failed. Unlike `Missed`, this indicates an
active attempt that couldn't complete.
For example, when a user requests `--use-max-register=100` but the allocator
cannot satisfy the constraint:
```
[Failed] Category:RegisterAllocator | Reason="Limiting to use-max-register=100 failed; it now uses 104 registers for better performance"
```
### Analysis
Neutral informational outputuseful for profiling and debugging.
```
[Analysis] Category:Register | Remark="Kernel uses 168 registers"
[Analysis] Category:Register | Remark="Kernel uses 10kB local memory"
```
***
## Emitting Remarks
Use the `remark::*` helpers to create an **in-flight remark**, then append
content with the `<<` operator.
### Configuring Remark Options
Each remark accepts four fields (all `StringRef`):
| Field | Description |
|***************-|************************************************|
| **Name** | Identifiable name for the remark |
| **Category** | High-level classification |
| **Sub-category** | Fine-grained classification |
| **Function** | The function where the remark originates |
### Basic Example
```c++
#include "mlir/IR/Remarks.h"
LogicalResult MyPass::runOnOperation() {
Location loc = getOperation()->getLoc();
auto opts = remark::RemarkOpts::name("VectorizeLoop")
.category("Vectorizer")
.subCategory("MyPass")
.function("foo");
// Passed: transformation succeeded
remark::passed(loc, opts)
<< "vectorized loop"
<< remark::metric("tripCount", 128);
// Analysis: informational output
remark::analysis(loc, opts)
<< "Kernel uses 168 registers";
// Missed: optimization skipped (with reason and suggestion)
remark::missed(loc, opts)
<< remark::reason("tripCount={0} < threshold={1}", 4, 256)
<< remark::suggest("increase unroll factor to {0}", 128);
// Failure: optimization attempted but failed
remark::failed(loc, opts)
<< remark::reason("unsupported pattern encountered");
return success();
}
```
***
## Metrics and Helpers
All helper functions accept
[LLVM format strings](https://llvm.org/docs/ProgrammersManual.html#formatting-strings-the-formatv-function),
which build lazilyensuring zero cost when remarks are disabled.
| Helper | Description |
|******************************--|******************************************|
| `remark::metric(key, value)` | Adds a structured keyvalue pair |
| `remark::add(fmt, ...)` | Shortcut for `metric("Remark", ...)` |
| `remark::reason(fmt, ...)` | Shortcut for `metric("Reason", ...)` |
| `remark::suggest(fmt, ...)` | Shortcut for `metric("Suggestion", ...)` |
### String Shorthand
Appending a plain string:
```c++
remark::passed(loc, opts) << "vectorized loop";
```
is equivalent to:
```c++
remark::passed(loc, opts) << remark::metric("Remark", "vectorized loop");
```
### Custom Metrics
Add structured data for machine readability:
```c++
remark::passed(loc, opts)
<< "loop optimized"
<< remark::metric("TripCount", 128)
<< remark::metric("VectorWidth", 4);
```
***
## Emitting Policies
The `RemarkEngine` supports pluggable policies that control which remarks are
emitted.
### RemarkEmittingPolicyAll
Emits **all** remarks unconditionally.
### RemarkEmittingPolicyFinal
Emits only the **final** remark for each location. This is useful in multi-pass
compilers where an early pass may report a failure, but a later pass succeeds.
**Example:** Only the successful remark is emitted:
```c++
auto opts = remark::RemarkOpts::name("Unroller").category("LoopUnroll");
// First pass: reports failure
remark::failed(loc, opts) << "Loop could not be unrolled";
// Later pass: reports success (this is the one emitted)
remark::passed(loc, opts) << "Loop unrolled successfully";
```
You can also implement custom policies by inheriting from the policy interface.
***
## Enabling Remarks
### Option 1: LLVM Remark Streamer (YAML or Bitstream)
Persist remarks to a file for post-processing:
```c++
// Setup categories
remark::RemarkCategories cats{
/*passed=*/ "LoopUnroll",
/*missed=*/ std::nullopt,
/*analysis=*/ std::nullopt,
/*failed=*/ "LoopUnroll"
};
// Use final policy
std::unique_ptr<remark::RemarkEmittingPolicyFinal> policy =
std::make_unique<remark::RemarkEmittingPolicyFinal>();
remark::enableOptimizationRemarksWithLLVMStreamer(
context, outputFile, llvm::remarks::Format::YAML, std::move(policy), cats);
```
**YAML output** (human-readable):
```yaml
*** !Passed
pass: Vectorizer:MyPass
name: VectorizeLoop
function: foo
loc: input.mlir:12:3
args:
- Remark: vectorized loop
- tripCount: 128
```
**Bitstream format** compact binary for large-scale analysis.
### Option 2: Diagnostic Engine (No Streamer)
Mirror remarks to the standard diagnostic output:
```c++
// Setup categories
remark::RemarkCategories cats{
/*passed=*/ "LoopUnroll",
/*missed=*/ std::nullopt,
/*analysis=*/ std::nullopt,
/*failed=*/ "LoopUnroll"
};
// Use final policy
std::unique_ptr<remark::RemarkEmittingPolicyFinal> policy =
std::make_unique<remark::RemarkEmittingPolicyFinal>();
remark::enableOptimizationRemarks(
context,
/*streamer=*/ nullptr,
/*policy=*/ std::move(policy),
cats,
/*printAsEmitRemarks=*/ true);
```
### Option 3: Custom Streamer
Implement your own backend for specialized output formats:
```c++
class MyStreamer : public MLIRRemarkStreamerBase {
public:
void streamOptimizationRemark(const Remark &remark) override {
// Custom serialization logic
}
};
auto streamer = std::make_unique<MyStreamer>();
remark::enableOptimizationRemarks(context, std::move(streamer), cats);
```