Stock linalg.matmul end to end
A stock linalg.matmul becomes an executable through mlir-opt, mlir-translate and clang before any custom lowering.
Start from stock IR
mlir/matmul.mlir holds a fixed 16x16x16 linalg.matmul on memrefs, with a func.func entry point marked for the C interface:
func.func @matmul(%A: memref<16x16xf32>, %B: memref<16x16xf32>, %C: memref<16x16xf32>)
attributes {llvm.emit_c_interface} {
linalg.matmul ins(%A, %B : memref<16x16xf32>, memref<16x16xf32>)
outs(%C : memref<16x16xf32>)
return
}
No custom dialect, no hand-written arith, no C++ pass. The claim under test is that the stock pipeline reaches an executable at all, so the IR should be as boring as possible. Sixteen cubed keeps the printed IR small enough to read by hand.
The executable should print a few elements of C, and those elements should match the scalar kernel from the matmul baseline at the same shape. The stock path gets checked against the same oracle as everything else, not against itself.
The pipeline
Lower with mlir-opt, translate, link, run:
mlir-opt matmul.mlir \
--convert-linalg-to-loops \
--convert-scf-to-cf \
--convert-cf-to-llvm \
--convert-func-to-llvm='use-bare-ptr-memref-call-conv=1' \
--finalize-memref-to-llvm \
--reconcile-unrealized-casts \
| mlir-translate --mlir-to-llvmir > matmul.ll
clang --target=riscv64-linux-gnu -march=rv64gcv -static matmul.ll main.c -o matmul
qemu-riscv64 -cpu rv64,v=true,vlen=256,elen=64,vext_spec=v1.0 ./matmul
use-bare-ptr-memref-call-conv=1 yields void matmul(float*, float*, float*) instead of a memref descriptor struct, so main.c needs no MLIR runtime headers. If clang cannot find the cross sysroot, pass the same --gcc-toolchain= you configured for riscv64-linux-gnu-gcc.
Confirm every name against your build with mlir-opt --help, and record the working pass list and the LLVM commit in VERSIONS.md. If a pass insists on running inside a nested op, the error names the anchor it wants, and --pass-pipeline='builtin.module(func.func(...))' is how you place it there.
Things that break
mlir-opt exits zero while leaving memref ops behind, and only mlir-translate fails. Check the intermediate IR for memref. after the memref conversion, before translation. If instead every pass is reported as unknown, the pipeline belongs to a different release, not to a broken IR.
Sources
- MLIR: using mlir-optdocs
- linalg dialect documentationdocs
- MLIR pass infrastructure and textual pipelinesdocs
A version records the documentation the claim was checked against, not a house pin. Confirm flags with --help on the release you installed.
Check yourself
Why get the stock path running before writing any custom lowering?
The final IR still contains `builtin.unrealized_conversion_cast`. What does that mean?
Why is a pipeline copied from a blog post or an old tutorial a risk?