MLIR · developer tooling
Compiler Tooling Lab
Three pinned MLIR projects, read as one pipeline: source, diagnostics, MLIR, pass inspection, profiling.
This page follows three separate compiler projects through the stages of a developer-tooling pipeline. Each project is pinned to one commit. Every output quoted below was captured from the project's own tools at that commit, or is a file shown verbatim from its repository. Where something could not be captured, the page says so instead of filling the gap.
- Source. JSON Schema constraints as schema-dialect IR.
- Diagnostics. Verifier errors with file, line and column.
- MLIR. Custom dialects lowered to the LLVM dialect.
- Pass inspection. IR snapshots and structural diffs, per pass.
- Profiling. Compile time per pass, plus runtime numbers where they exist.
1. json-schema-mlir
JSON Schema constraints as an MLIR dialect, optimized as IR and lowered to native code.
Source · Diagnostics · MLIRv0.1.0-12-g6f4be81 · Source at 6f4be81 · README.md · Architecture · Run it locally · Tests and benchmarks
json-schema-mlir represents JSON Schema validation rules as operations in a schema dialect. Constraints are optimized as IR (subsumption, fusion, removing redundant checks) before being lowered to arith/scf/math and the LLVM dialect.
The same constraint lattice also exists as a Mojo library (mojo/schema/), with the canonicalizer's subsumption and meet rules and the lowered validation semantics. Its tests check that merging two constraints never changes which values are accepted.
At the pinned commit, the entry point is schema-opt on .mlir input. The JSON-to-IR front end shown in the upstream pipeline diagram is not in the tree yet.
Follow one real test function from the dialect, through canonicalization and lowering, to the LLVM dialect, plus one verifier diagnostic.
Start from a real test input
Three validate_string checks on the same value, combined with arith.andi. This function comes verbatim from the repository's canonicalization tests.
Diagnostics point at the source
A contradictory constraint (max_length below min_length) is rejected by the op verifier. The error carries file, line and column. This input file was written for the lab; the diagnostic is schema-opt's real output.
Every pass in the pipeline
--schema-to-std-pipeline runs the passes below. Rows marked unchanged left the IR exactly as they found it.
Canonicalization fuses the constraint tree
The three checks become one validate_string carrying the meet of the constraints: min_length 5 subsumes min_length 2. Both andi ops disappear.
Lowering guards on the runtime type tag
The validator becomes an scf.if on the JSON kind, so a length check never runs on a non-string. String length and regex matching go through the runtime ABI.
Down to the LLVM dialect
--schema-to-llvm-pipeline continues through control flow and LLVM conversion.
The same lattice in Mojo
The Mojo library repeats each canonicalization test case, then checks a grid of constraint pairs and values, including NaN and infinity: whenever two constraints merge, the merged one accepts exactly what both accepted.
2. VizMLIR
Browser-only MLIR graph viewer and structural diff, with a Rust parser compiled to WebAssembly.
Pass inspectionv0.3.0-14-gb4f2c91 · Source at b4f2c91 · README.md · Architecture · Run it locally · Tests and benchmarks
VizMLIR parses MLIR in the browser with a Rust parser compiled to WebAssembly, draws the operation graph on a canvas, and diffs two IR snapshots with SSA names normalized. No backend is involved.
In this lab it is the pass-inspection step: the IR captured from json-schema-mlir and nano-dsp-mlir is run through VizMLIR's own WASM parser and diff module at the pinned commit.
VizMLIR has a live app: joepothiboot.github.io/vizmlir/. It deploys from the project's main branch, so it can be newer than the pinned b4f2c91 described here.
Diff real compiler passes from the other projects with VizMLIR's own WASM parser, then open the app and try it yourself.
Diff json-schema-mlir's canonicalization
VizMLIR matches operations by kind and SSA-normalized label. The fused validate_string keeps its op name, so it matches. The two absorbed checks and both andi ops are reported as removed.
A real finding: function arguments
In the graph summaries above, VizMLIR reports each use of %arg0 as an undefined SSA value. At this commit the parser does not appear to register function block arguments as definitions. This is captured output, shown as-is, and a candidate upstream fix.
Diff the lowering to standard dialects
The larger diff after --lower-schema-to-std shows the runtime ABI declarations and the scf.if guard being introduced.
Diff nano-dsp-mlir's dsp → linalg conversion
The same diff applied to the other compiler: dsp ops become linalg.generic with affine maps.
3. nano-dsp-mlir
A small image/math DSL dialect lowered to linalg and LLVM, with differential execution tests.
MLIR · Pass inspection · Profilingv0.1.0-6-gccb09e1 · Source at ccb09e1 · README.md · Architecture · Run it locally · Tests and benchmarks
nano-dsp-mlir defines a dsp dialect (add, relu, matmul, conv2d) with shape verifiers, and lowers it to linalg.generic. From there the tests lower through stock MLIR passes to LLVM and execute with mlir-runner to check numeric results.
Each dsp op is also implemented as a SIMD Mojo kernel (mojo/nanodsp/) and as a plain C++ loop nest (reference/). All three implementations are tested against the same expected values, and the Mojo kernels are benchmarked. The upstream README also describes pieces not in the tree yet: transform-dialect schedules, -nanodsp-optimize, the Python DSL and sweep scripts.
Follow relu(conv2d(image) + bias) from the dsp dialect through 19 passes to the LLVM dialect, execute it, and see where compile time goes.
The program
An integration test: a 3×3 box blur over a 4×4 image, plus a bias, through relu. The CHECK lines state the expected output.
Verifier diagnostics
The invalid-op tests, run without -verify-diagnostics, so the raw errors and their locations are visible.
dsp → linalg
The only pass this project owns. conv2d, add and relu become linalg.generic ops with explicit indexing maps.
Every pass to the LLVM dialect
The stock lowering chain the tests use. Unchanged rows are passes that found nothing to do.
Execute and check
The lowered module run with mlir-runner. The printed memref is compared with the test's CHECK lines.
The same ops in Mojo and C++
The Mojo kernels and the C++ reference are checked against the same expected values as the MLIR tests above. The Mojo tests also compare against a naive loop nest on odd sizes, so the code after each SIMD chunk runs too.
Profiling
Compile-time wall clock per pass, from one run on the capture host. It shows relative cost, not a benchmark. The kernel numbers are the untiled Mojo kernels on one core, the baseline the planned tiling stage has to beat.