MLIR

Multi-Level IR Compiler Framework

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, 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.
  • KindsPassed, Missed, Failure, Analysis.
  • API – Lightweight streaming interface using << (like MLIR diagnostics).

How It Works 

Two main components:

  • RemarkEngine (owned by MLIRContext): Receives finalized InFlightRemarks, optionally mirrors them to the DiagnosticEngine, and dispatches to the installed streamer.

  • MLIRRemarkStreamerBase (abstract): Backend interface with a single hook:

    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: MLIRContextRemarkEngineMLIRRemarkStreamerBase


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:

$ 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 

#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 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:

remark::metric("TripCount", value) 

String Metrics 

Passing a plain string (e.g. << "vectorized loop") is equivalent to:

metric("Remark", "vectorized loop") 

Enabling Remarks 

1. With LLVMRemarkStreamer (YAML or Bitstream) 

Persists remarks to a file in the chosen format.

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:

--- !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

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.

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);