| # Remark Infrastructure |
| |
| Remarks are **structured, human- and machine-readable notes** emitted by the |
| compiler to explain: |
| |
| - What was transformed |
| - What was missed |
| - Why it happened |
| |
| The **`RemarkEngine`** collects finalized remarks during compilation and sends |
| them to a pluggable **streamer**. By default, MLIR integrates with LLVM’s |
| [`llvm::remarks`](https://llvm.org/docs/Remarks.html), allowing you to: |
| |
| - Stream remarks as passes run |
| - Serialize them to **YAML** or **LLVM bitstream** for tooling |
| |
| *** |
| |
| ## Key Points |
| |
| - **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). |
| |
| *** |
| |
| ## How It Works |
| |
| Two main components: |
| |
| - **`RemarkEngine`** (owned by `MLIRContext`): Receives finalized |
| `InFlightRemark`s, optionally mirrors them to the `DiagnosticEngine`, and |
| dispatches to the installed streamer. |
| |
| - **`MLIRRemarkStreamerBase`** (abstract): Backend interface with a single hook: |
| |
| ```c++ |
| virtual void streamOptimizationRemark(const Remark &remark) = 0; |
| ``` |
| |
| **Default backend – `MLIRLLVMRemarkStreamer`** Adapts `mlir::Remark` to LLVM’s |
| remark format and writes YAML/bitstream via `llvm::remarks::RemarkStreamer`. |
| |
| **Ownership flow:** `MLIRContext` → `RemarkEngine` → `MLIRRemarkStreamerBase` |
| |
| *** |
| |
| ## Categories |
| |
| MLIR provides four built-in remark categories (extendable if needed): |
| |
| #### 1. **Passed** |
| |
| Optimization/transformation succeeded. |
| |
| ``` |
| [Passed] RemarkName | Category:Vectorizer:myPass1 | Function=foo | Remark="vectorized loop", tripCount=128 |
| ``` |
| |
| #### 2. **Missed** |
| |
| Optimization/transformation didn’t apply — ideally with actionable feedback. |
| |
| ``` |
| [Missed] | Category:Unroll | Function=foo | Reason="tripCount=4 < threshold=256", Suggestion="increase unroll to 128" |
| ``` |
| |
| #### 3. **Failure** |
| |
| Optimization/transformation attempted but failed. This is slightly different |
| from the `Missed` category. |
| |
| For example, the user specifies `-use-max-register=100` when invoking the |
| compiler, but the attempt fails for some reason: |
| |
| ```bash |
| $ your-compiler -use-max-register=100 mycode.xyz |
| ``` |
| |
| ``` |
| [Failed] Category:RegisterAllocator | Reason="Limiting to use-max-register=100 failed; it now uses 104 registers for better performance" |
| ``` |
| |
| #### 4. **Analysis** |
| |
| Neutral analysis results. |
| |
| ``` |
| [Analysis] Category:Register | Remark="Kernel uses 168 registers" |
| [Analysis] Category:Register | Remark="Kernel uses 10kB local memory" |
| ``` |
| |
| *** |
| |
| ## Emitting Remarks |
| |
| The `remark::*` helpers return an **in-flight remark**. |
| You append strings or key–value metrics using `<<`. |
| |
| ### Remark Options |
| |
| When constructing a remark, you typically provide four fields that are `StringRef`: |
| |
| 1. **Remark name** – identifiable name |
| 2. **Category** – high-level classification |
| 3. **Sub-category** – more fine-grained classification |
| 4. **Function name** – the function where the remark originates |
| |
| |
| ### Example |
| |
| ```c++ |
| #include "mlir/IR/Remarks.h" |
| |
| LogicalResult MyPass::runOnOperation() { |
| Location loc = getOperation()->getLoc(); |
| |
| remark::RemarkOpts opts = remark::RemarkOpts::name(MyRemarkName1) |
| .category(categoryVectorizer) |
| .function(fName) |
| .subCategory(myPassname1); |
| |
| // PASSED |
| remark::passed(loc, opts) |
| << "vectorized loop" |
| << remark::metric("tripCount", 128); |
| |
| // ANALYSIS |
| remark::analysis(loc, opts) |
| << "Kernel uses 168 registers"; |
| |
| // MISSED (with reason + suggestion) |
| int tripBad = 4, threshold = 256, target = 128; |
| remark::missed(loc, opts) |
| << remark::reason("tripCount={0} < threshold={1}", tripBad, threshold) |
| << remark::suggest("increase unroll to {0}", target); |
| |
| // FAILURE |
| remark::failed(loc, opts) |
| << remark::reason("failed due to unsupported pattern"); |
| |
| return success(); |
| } |
| ``` |
| |
| *** |
| |
| ### Metrics and Shortcuts |
| |
| Helper functions accept |
| [LLVM format](https://llvm.org/docs/ProgrammersManual.html#formatting-strings-the-formatv-function) |
| style strings. This format builds lazily, so remarks are zero-cost when |
| disabled. |
| |
| #### Adding Remarks |
| |
| - **`remark::add(fmt, ...)`** – Shortcut for `metric("Remark", ...)`. |
| |
| #### Adding Reasons |
| |
| - **`remark::reason(fmt, ...)`** – Shortcut for `metric("Reason", ...)`. Used to |
| explain why a remark was missed or failed. |
| |
| #### Adding Suggestions |
| |
| - **`remark::suggest(fmt, ...)`** – Shortcut for `metric("Suggestion", ...)`. |
| Used to provide actionable feedback. |
| |
| #### Adding Custom Metrics |
| |
| - **`remark::metric(key, value)`** – Adds a structured key–value metric. |
| |
| Example: tracking `TripCount`. When exported to YAML, it appears under `args` |
| for machine readability: |
| |
| ```cpp |
| remark::metric("TripCount", value) |
| ``` |
| |
| #### String Metrics |
| |
| Passing a plain string (e.g. `<< "vectorized loop"`) is equivalent to: |
| |
| ```cpp |
| metric("Remark", "vectorized loop") |
| ``` |
| |
| *** |
| |
| ## Enabling Remarks |
| |
| ### 1. **With LLVMRemarkStreamer (YAML or Bitstream)** |
| |
| Persists remarks to a file in the chosen format. |
| |
| ```c++ |
| mlir::remark::RemarkCategories cats{/*passed=*/categoryLoopunroll, |
| /*missed=*/std::nullopt, |
| /*analysis=*/std::nullopt, |
| /*failed=*/categoryLoopunroll}; |
| |
| mlir::remark::enableOptimizationRemarksWithLLVMStreamer( |
| context, yamlFile, llvm::remarks::Format::YAML, cats); |
| ``` |
| |
| **YAML format** – human-readable, easy to diff: |
| |
| ```yaml |
| --- !Passed |
| pass: Category:SubCategory |
| name: MyRemarkName1 |
| function: myFunc |
| loc: myfile.mlir:12:3 |
| args: |
| - Remark: vectorized loop |
| - tripCount: 128 |
| ``` |
| |
| **Bitstream format** – compact binary for large runs. |
| |
| *** |
| |
| ### 2. **With `mlir::emitRemarks` (No Streamer)** |
| |
| If the streamer isn't passed, the remarks are mirrored to the `DiagnosticEngine` |
| using `mlir::emitRemarks` |
| |
| ```c++ |
| mlir::remark::RemarkCategories cats{/*passed=*/categoryLoopunroll, |
| /*missed=*/std::nullopt, |
| /*analysis=*/std::nullopt, |
| /*failed=*/categoryLoopunroll}; |
| remark::enableOptimizationRemarks( |
| /*streamer=*/nullptr, cats, |
| /*printAsEmitRemarks=*/true); |
| ``` |
| |
| *** |
| |
| ### 3. **With a Custom Streamer** |
| |
| You can implement a custom streamer by inheriting `MLIRRemarkStreamerBase` to |
| consume remarks in any format. |
| |
| ```c++ |
| class MyStreamer : public MLIRRemarkStreamerBase { |
| public: |
| void streamOptimizationRemark(const Remark &remark) override { |
| // Convert and write remark to your custom format |
| } |
| }; |
| |
| auto myStreamer = std::make_unique<MyStreamer>(); |
| remark::enableOptimizationRemarks( |
| /*streamer=*/myStreamer, cats, |
| /*printAsEmitRemarks=*/true); |
| ``` |