MLIR
Contract
| Field | Bound contract |
|---|---|
| Trigger | A user builds a domain-specific compiler IR, lowers high-level ops toward LLVM or GPU dialects, writes a progressive lowering pipeline, defines ops in TableGen, or connects a PyTorch or ONNX model to an MLIR-based compiler. |
| Authority | Reversible local: writes only the dialect, pass, and test files the user names inside the project, plus generated .inc files and scratch .mlir outputs in the build directory; rollback is version control and deleting the build directory. No remote mutation. |
| Side effect | mlir-tblgen generates op code, the dialect and passes are built, and mlir-opt runs the pipeline on test IR with the verifier on. |
| Done | Test IR parses, the pipeline leaves no illegal op, the output translates to LLVM IR when the target is the LLVM dialect, and every custom op round-trips through its assembly format. |
Inputs
- The IR level to work at (required): existing dialects only, or a custom dialect with named ops.
- Source and target dialects of the lowering (required when lowering): for example
linalgon tensors down to thellvmdialect. - Test IR (required): one
.mlirinput per pipeline stage. - Installed MLIR (gathered by the skill):
mlir-opt --version. Grounded current stable is LLVM/MLIR 23.1.0; pass names and TableGen directives below are confirmed against it.
Procedure
Read the IR by its four nouns. A module holds operations; an operation such as
arith.addiormemref.loadproduces SSA values and may hold regions; a region holds blocks; a block holds an ordered list of operations and ends in a terminator. Block arguments replace PHI nodes. Afunc.funcis an operation with one region. Done when: the input is described in these terms and its dialects are listed.Pick the built-in dialects that fit the level:
arithfor scalar arithmetic,funcfor functions and calls,memreffor buffers with shape and stride,tensorfor value-semantic arrays,affinefor affine loop nests and maps,linalgfor structured operations such aslinalg.matmul,scffor structuredforandif,cffor unstructured branches,gpufor kernel launches,llvmfor the final form. A loop over buffers reads as:func.func @add(%a: memref<4xf32>, %b: memref<4xf32>, %c: memref<4xf32>) { %c0 = arith.constant 0 : index %c1 = arith.constant 1 : index %c4 = arith.constant 4 : index scf.for %i = %c0 to %c4 step %c1 { %av = memref.load %a[%i] : memref<4xf32> %bv = memref.load %b[%i] : memref<4xf32> %sum = arith.addf %av, %bv : f32 memref.store %sum, %c[%i] : memref<4xf32> } return }Done when:
mlir-opt example.mlirparses and prints the file unchanged.Drive lowering with
mlir-opt, one stage per flag, and read the output between stages:mlir-opt example.mlir -canonicalize mlir-opt affine.mlir --lower-affine mlir-opt matmul.mlir --one-shot-bufferize="bufferize-function-boundaries" --convert-linalg-to-loops -o loops.mlir mlir-opt loops.mlir --convert-scf-to-cf --convert-arith-to-llvm --finalize-memref-to-llvm \ --convert-cf-to-llvm --convert-func-to-llvm --reconcile-unrealized-casts -o llvm.mlir mlir-translate --mlir-to-llvmir llvm.mlir -o out.llThe path from a tensor-level
linalg.matmulis: bufferize (tensors to memrefs), lowerlinalgtoscforaffineloops, lower structured control flow tocf, then convert each remaining dialect tollvm, and finish with--reconcile-unrealized-casts.--convert-to-llvmruns the dialect-provided conversions in one step when every dialect in the input implements the interface. Done when:mlir-translate --mlir-to-llvmiremits adefinefor each function.Define custom ops in ODS (TableGen). Include the side-effect interface for
Pure, and either constrain operand types or make the assembly format name every type; withAnyTypeoperands,type($result)alone cannot be parsed unless a trait ties the types together:include "mlir/IR/OpBase.td" include "mlir/Interfaces/SideEffectInterfaces.td" def My_Dialect : Dialect { let name = "my"; let cppNamespace = "::my"; } class My_Op<string mnemonic, list<Trait> traits = []> : Op<My_Dialect, mnemonic, traits>; def AddOp : My_Op<"add", [Pure, AllTypesMatch<["lhs", "rhs", "result"]>]> { let summary = "Add two values"; let arguments = (ins AnyType:$lhs, AnyType:$rhs); let results = (outs AnyType:$result); let assemblyFormat = "$lhs `,` $rhs attr-dict `:` type($result)"; }mlir-tblgen -gen-dialect-decls MyOps.td -I $(llvm-config --includedir) -o MyDialect.h.inc mlir-tblgen -gen-dialect-defs MyOps.td -I $(llvm-config --includedir) -o MyDialect.cpp.inc mlir-tblgen -gen-op-decls MyOps.td -I $(llvm-config --includedir) -o MyOps.h.inc mlir-tblgen -gen-op-defs MyOps.td -I $(llvm-config --includedir) -o MyOps.cpp.incDone when: all four generators run without a diagnostic.
Register the ops in the dialect's C++ initialization and include the generated definitions once:
#include "mlir/IR/DialectImplementation.h" #include "MyDialect.h" #define GET_OP_CLASSES #include "MyOps.cpp.inc" void my::MyDialect::initialize() { addOperations< #define GET_OP_LIST #include "MyOps.cpp.inc" >(); }Every tool that parses the dialect must register it (
registry.insert<my::MyDialect>()); an unregistered dialect fails at parse with "Dialect not found". Done when:mlir-optbuilt with the dialect parses amy.addop and prints it back.Lower custom ops with the dialect conversion framework. A pattern rewrites one op; the target marks the source dialect illegal and the destination legal; partial conversion tolerates unrelated ops, full conversion fails on any illegal op left:
#include "mlir/Transforms/DialectConversion.h" struct AddOpLowering : mlir::OpConversionPattern<my::AddOp> { using OpConversionPattern::OpConversionPattern; mlir::LogicalResult matchAndRewrite(my::AddOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const override { rewriter.replaceOpWithNewOp<mlir::arith::AddIOp>(op, adaptor.getLhs(), adaptor.getRhs()); return mlir::success(); } }; void MyLoweringPass::runOnOperation() { mlir::ConversionTarget target(getContext()); target.addIllegalDialect<my::MyDialect>(); target.addLegalDialect<mlir::arith::ArithDialect>(); mlir::RewritePatternSet patterns(&getContext()); patterns.add<AddOpLowering>(&getContext()); if (mlir::failed(mlir::applyPartialConversion(getOperation(), target, std::move(patterns)))) signalPassFailure(); }Use the adaptor's typed accessors for operands; they carry the already-converted values, while
op.getLhs()still holds the old ones. Done when: the pass leaves nomy.op in the output of the test input.For ML front ends, import the model and hand the MLIR to the compiler. Torch-MLIR turns PyTorch or ONNX graphs into the
torchdialect and lowers tolinalg; IREE compiles MLIR to a deployable module and runs it. Use each project's current CLI as documented in its own repository; their command names and flags change between releases and are not pinned here. Done when: the imported module parses with the project'smlir-optbuild and the lowering from step 3 applies.
Failure and recovery
| Failure class | Behavior |
|---|---|
Dialect not found at parse |
The dialect is not registered in the tool. Register it in the tool's dialect registry or in the pass's getDependentDialects. |
mlir-tblgen include error |
The -I path does not reach mlir/IR/OpBase.td. Pass $(llvm-config --includedir). |
| Type not buildable in assembly format | An AnyType operand has no type directive. Add a type-matching trait or name each type($operand) in the format. |
| Illegal ops remain after lowering | A pattern is missing. Run with --mlir-print-ir-after-failure to see the op left; add the pattern or mark the op legal. |
mlir-opt crashes |
Invalid IR between passes. Run with --verify-each (on by default) and --mlir-print-ir-after-all to find the pass that produced it. |
cf ops rejected before LLVM translation |
--convert-cf-to-llvm is missing. Add it after --convert-scf-to-cf. |
No partial result is claimed complete. If a step cannot finish, the report states which steps passed and which are blocked.
Output
An MLIR delivery containing:
- Files written:
.td, dialect and pass sources, tests, and the generated.incfiles. - Pipeline: the exact
mlir-optflag sequence and the per-stage output files. - Verification: parse round-trip of custom ops, absence of illegal ops after lowering, and the
mlir-translateresult. - Open items: any op left at a higher level with the reason.