cp-13 · MLIR foundations — emit, lower, translate

cp-11/12 used LLVM IR directly. cp-13 takes a step up the abstraction ladder to MLIR (Multi-Level Intermediate Representation): a generic framework for building IRs with first-class regions, blocks, and extensible dialects.

We emit our TAC IR as MLIR text in the llvm dialect (a near-1:1 mapping of LLVM IR into MLIR syntax), then drive Homebrew's mlir-translate + lli to execute the program.

Why the llvm dialect?

Real MLIR projects build a custom dialect (minilang.*) and lower it through arith, cf, memref to llvm. That's pedagogically fantastic but operationally fragile across MLIR versions. cp-13 keeps the toolchain minimal so every test passes out-of-the-box on LLVM/MLIR 20. The step docs walk through what a full dialect would look like.

Build & run

cmake -S src/cpp -B build
cmake --build build
./build/tests/test_mlir_emit               # → 25/25 checks passed

echo 'print 2+3*4;' | ./build/mlmlir       # emit MLIR
echo 'print 2+3*4;' | ./build/mlmlir --run # → 14

Inspect the pipeline by hand:

echo 'print 42;' | ./build/mlmlir > /tmp/m.mlir
/opt/homebrew/opt/llvm/bin/mlir-opt /tmp/m.mlir --canonicalize
/opt/homebrew/opt/llvm/bin/mlir-translate /tmp/m.mlir --mlir-to-llvmir
/opt/homebrew/opt/llvm/bin/mlir-translate /tmp/m.mlir --mlir-to-llvmir \
  | /opt/homebrew/opt/llvm/bin/lli

Reading order

  1. steps/01-why-mlir.md
  2. steps/02-ir-shape.md
  3. steps/03-dialects.md
  4. steps/04-llvm-dialect-mapping.md
  5. steps/05-pipeline-mlir-translate.md
  6. steps/06-progressive-lowering.md
  7. steps/07-when-to-reach-for-mlir.md