Skip to content

Commit 3d41197

Browse files
authored
[MLIR] Introduce RemarkEngine + pluggable remark streaming (YAML/Bitstream) (#152474)
This PR implements structured, tooling-friendly optimization remarks with zero cost unless enabled. It implements: - `RemarkEngine` collects finalized remarks within `MLIRContext`. - `MLIRRemarkStreamerBase` abstract class streams them to a backend. - Backends: `MLIRLLVMRemarkStreamer` (bridges to llvm::remarks → YAML/Bitstream) or your own custom streamer. - Optional mirroring to DiagnosticEngine (printAsEmitRemarks + categories). - Off by default; no behavior change unless enabled. Thread-safe; ordering best-effort. ## Overview ``` Passes (reportOptimization*) │ ▼ +-------------------+ | RemarkEngine | collects +-------------------+ │ │ │ mirror │ stream ▼ ▼ emitRemark MLIRRemarkStreamerBase (abstract) │ ├── MLIRLLVMRemarkStreamer → llvm::remarks → YAML | Bitstream └── CustomStreamer → your sink ``` ## Enable Remark engine and Plug LLVM's Remark streamer ``` // Enable once per MLIRContext. This uses `MLIRLLVMRemarkStreamer` mlir::remark::enableOptimizationRemarksToFile( ctx, path, llvm::remarks::Format::YAML, cats); ``` ## API to emit remark ``` // Emit from a pass remark::passed(loc, categoryVectorizer, myPassname1) << "vectorized loop"; remark::missed(loc, categoryUnroll, "MyPass") << remark::reason("not profitable at this size") // Creates structured reason arg << remark::suggest("increase unroll factor to >=4"); // Creates structured suggestion arg remark::passed(loc, categoryVectorizer, myPassname1) << "vectorized loop" << remark::metric("tripCount", 128); // Create structured metric on-the-fly ```
1 parent 5d4aa87 commit 3d41197

File tree

12 files changed

+1538
-0
lines changed

12 files changed

+1538
-0
lines changed

mlir/docs/Remarks.md

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
# Remark Infrastructure
2+
3+
Remarks are **structured, human- and machine-readable notes** emitted by the
4+
compiler to explain:
5+
6+
- What was transformed
7+
- What was missed
8+
- Why it happened
9+
10+
The **`RemarkEngine`** collects finalized remarks during compilation and sends
11+
them to a pluggable **streamer**. By default, MLIR integrates with LLVM’s
12+
[`llvm::remarks`](https://llvm.org/docs/Remarks.html), allowing you to:
13+
14+
- Stream remarks as passes run
15+
- Serialize them to **YAML** or **LLVM bitstream** for tooling
16+
17+
***
18+
19+
## Key Points
20+
21+
- **Opt-in** – Disabled by default; zero overhead unless enabled.
22+
- **Per-context** – Configured on `MLIRContext`.
23+
- **Formats** – LLVM Remark engine (YAML / Bitstream) or custom streamers.
24+
- **Kinds**`Passed`, `Missed`, `Failure`, `Analysis`.
25+
- **API** – Lightweight streaming interface using `<<` (like MLIR diagnostics).
26+
27+
***
28+
29+
## How It Works
30+
31+
Two main components:
32+
33+
- **`RemarkEngine`** (owned by `MLIRContext`): Receives finalized
34+
`InFlightRemark`s, optionally mirrors them to the `DiagnosticEngine`, and
35+
dispatches to the installed streamer.
36+
37+
- **`MLIRRemarkStreamerBase`** (abstract): Backend interface with a single hook:
38+
39+
```c++
40+
virtual void streamOptimizationRemark(const Remark &remark) = 0;
41+
```
42+
43+
**Default backend – `MLIRLLVMRemarkStreamer`** Adapts `mlir::Remark` to LLVM’s
44+
remark format and writes YAML/bitstream via `llvm::remarks::RemarkStreamer`.
45+
46+
**Ownership flow:** `MLIRContext` → `RemarkEngine` → `MLIRRemarkStreamerBase`
47+
48+
***
49+
50+
## Categories
51+
52+
MLIR provides four built-in remark categories (extendable if needed):
53+
54+
#### 1. **Passed**
55+
56+
Optimization/transformation succeeded.
57+
58+
```
59+
[Passed] RemarkName | Category:Vectorizer:myPass1 | Function=foo | Remark="vectorized loop", tripCount=128
60+
```
61+
62+
#### 2. **Missed**
63+
64+
Optimization/transformation didn’t apply — ideally with actionable feedback.
65+
66+
```
67+
[Missed] | Category:Unroll | Function=foo | Reason="tripCount=4 < threshold=256", Suggestion="increase unroll to 128"
68+
```
69+
70+
#### 3. **Failure**
71+
72+
Optimization/transformation attempted but failed. This is slightly different
73+
from the `Missed` category.
74+
75+
For example, the user specifies `-use-max-register=100` when invoking the
76+
compiler, but the attempt fails for some reason:
77+
78+
```bash
79+
$ your-compiler -use-max-register=100 mycode.xyz
80+
```
81+
82+
```
83+
[Failed] Category:RegisterAllocator | Reason="Limiting to use-max-register=100 failed; it now uses 104 registers for better performance"
84+
```
85+
86+
#### 4. **Analysis**
87+
88+
Neutral analysis results.
89+
90+
```
91+
[Analysis] Category:Register | Remark="Kernel uses 168 registers"
92+
[Analysis] Category:Register | Remark="Kernel uses 10kB local memory"
93+
```
94+
95+
***
96+
97+
## Emitting Remarks
98+
99+
The `remark::*` helpers return an **in-flight remark**.
100+
You append strings or key–value metrics using `<<`.
101+
102+
### Remark Options
103+
104+
When constructing a remark, you typically provide four fields that are `StringRef`:
105+
106+
1. **Remark name** – identifiable name
107+
2. **Category** – high-level classification
108+
3. **Sub-category** – more fine-grained classification
109+
4. **Function name** – the function where the remark originates
110+
111+
112+
### Example
113+
114+
```c++
115+
#include "mlir/IR/Remarks.h"
116+
117+
LogicalResult MyPass::runOnOperation() {
118+
Location loc = getOperation()->getLoc();
119+
120+
remark::RemarkOpts opts = remark::RemarkOpts::name(MyRemarkName1)
121+
.category(categoryVectorizer)
122+
.function(fName)
123+
.subCategory(myPassname1);
124+
125+
// PASSED
126+
remark::passed(loc, opts)
127+
<< "vectorized loop"
128+
<< remark::metric("tripCount", 128);
129+
130+
// ANALYSIS
131+
remark::analysis(loc, opts)
132+
<< "Kernel uses 168 registers";
133+
134+
// MISSED (with reason + suggestion)
135+
int tripBad = 4, threshold = 256, target = 128;
136+
remark::missed(loc, opts)
137+
<< remark::reason("tripCount={0} < threshold={1}", tripBad, threshold)
138+
<< remark::suggest("increase unroll to {0}", target);
139+
140+
// FAILURE
141+
remark::failed(loc, opts)
142+
<< remark::reason("failed due to unsupported pattern");
143+
144+
return success();
145+
}
146+
```
147+
148+
***
149+
150+
### Metrics and Shortcuts
151+
152+
Helper functions accept
153+
[LLVM format](https://llvm.org/docs/ProgrammersManual.html#formatting-strings-the-formatv-function)
154+
style strings. This format builds lazily, so remarks are zero-cost when
155+
disabled.
156+
157+
#### Adding Remarks
158+
159+
- **`remark::add(fmt, ...)`** – Shortcut for `metric("Remark", ...)`.
160+
161+
#### Adding Reasons
162+
163+
- **`remark::reason(fmt, ...)`** – Shortcut for `metric("Reason", ...)`. Used to
164+
explain why a remark was missed or failed.
165+
166+
#### Adding Suggestions
167+
168+
- **`remark::suggest(fmt, ...)`** – Shortcut for `metric("Suggestion", ...)`.
169+
Used to provide actionable feedback.
170+
171+
#### Adding Custom Metrics
172+
173+
- **`remark::metric(key, value)`** – Adds a structured key–value metric.
174+
175+
Example: tracking `TripCount`. When exported to YAML, it appears under `args`
176+
for machine readability:
177+
178+
```cpp
179+
remark::metric("TripCount", value)
180+
```
181+
182+
#### String Metrics
183+
184+
Passing a plain string (e.g. `<< "vectorized loop"`) is equivalent to:
185+
186+
```cpp
187+
metric("Remark", "vectorized loop")
188+
```
189+
190+
***
191+
192+
## Enabling Remarks
193+
194+
### 1. **With LLVMRemarkStreamer (YAML or Bitstream)**
195+
196+
Persists remarks to a file in the chosen format.
197+
198+
```c++
199+
mlir::remark::RemarkCategories cats{/*passed=*/categoryLoopunroll,
200+
/*missed=*/std::nullopt,
201+
/*analysis=*/std::nullopt,
202+
/*failed=*/categoryLoopunroll};
203+
204+
mlir::remark::enableOptimizationRemarksWithLLVMStreamer(
205+
context, yamlFile, llvm::remarks::Format::YAML, cats);
206+
```
207+
208+
**YAML format** – human-readable, easy to diff:
209+
210+
```yaml
211+
--- !Passed
212+
pass: Category:SubCategory
213+
name: MyRemarkName1
214+
function: myFunc
215+
loc: myfile.mlir:12:3
216+
args:
217+
- Remark: vectorized loop
218+
- tripCount: 128
219+
```
220+
221+
**Bitstream format** – compact binary for large runs.
222+
223+
***
224+
225+
### 2. **With `mlir::emitRemarks` (No Streamer)**
226+
227+
If the streamer isn't passed, the remarks are mirrored to the `DiagnosticEngine`
228+
using `mlir::emitRemarks`
229+
230+
```c++
231+
mlir::remark::RemarkCategories cats{/*passed=*/categoryLoopunroll,
232+
/*missed=*/std::nullopt,
233+
/*analysis=*/std::nullopt,
234+
/*failed=*/categoryLoopunroll};
235+
remark::enableOptimizationRemarks(
236+
/*streamer=*/nullptr, cats,
237+
/*printAsEmitRemarks=*/true);
238+
```
239+
240+
***
241+
242+
### 3. **With a Custom Streamer**
243+
244+
You can implement a custom streamer by inheriting `MLIRRemarkStreamerBase` to
245+
consume remarks in any format.
246+
247+
```c++
248+
class MyStreamer : public MLIRRemarkStreamerBase {
249+
public:
250+
void streamOptimizationRemark(const Remark &remark) override {
251+
// Convert and write remark to your custom format
252+
}
253+
};
254+
255+
auto myStreamer = std::make_unique<MyStreamer>();
256+
remark::enableOptimizationRemarks(
257+
/*streamer=*/myStreamer, cats,
258+
/*printAsEmitRemarks=*/true);
259+
```

mlir/include/mlir/IR/MLIRContext.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ class MLIRContextImpl;
3434
class RegisteredOperationName;
3535
class StorageUniquer;
3636
class IRUnit;
37+
namespace remark::detail {
38+
class RemarkEngine;
39+
} // namespace remark::detail
3740

3841
/// MLIRContext is the top-level object for a collection of MLIR operations. It
3942
/// holds immortal uniqued objects like types, and the tables used to unique
@@ -212,6 +215,13 @@ class MLIRContext {
212215
/// Returns the diagnostic engine for this context.
213216
DiagnosticEngine &getDiagEngine();
214217

218+
/// Returns the remark engine for this context, or nullptr if none has been
219+
/// set.
220+
remark::detail::RemarkEngine *getRemarkEngine();
221+
222+
/// Set the remark engine for this context.
223+
void setRemarkEngine(std::unique_ptr<remark::detail::RemarkEngine> engine);
224+
215225
/// Returns the storage uniquer used for creating affine constructs.
216226
StorageUniquer &getAffineUniquer();
217227

0 commit comments

Comments
 (0)