This page was generated from tools/cli/apply_passes_to_string_or_file.ipynb.

Applying Passes to MLIR Code

This tutorial will go over how to apply passes to MLIR code as a string or stored in files on disk using the mlir-opt CLI Wrapper.

Let’s first import some necessary modules and generate an instance of our mlir-opt CLI Wrapper.


import os
import tempfile
from mlir_graphblas import MlirOptCli

cli = MlirOptCli(executable=None, options=None)
Using development graphblas-opt: /Users/pnguyen/code/mlir-graphblas/mlir_graphblas/src/build/bin/graphblas-opt

Applying Passes to MLIR Strings

Let’s say we had the following MLIR code in Python as a string. The function scale_func scales each element of the given tensor by the given scalar.


mlir_string = """
#trait_1d_scalar = {
  indexing_maps = [
    affine_map<(i) -> (i)>,  // A
    affine_map<(i) -> (i)>   // X (out)
  ],
  iterator_types = ["parallel"],
  doc = "X(i) = A(i) OP Scalar"
}
func @scale_func(%input: tensor<?xf32>, %scale: f32) -> tensor<?xf32> {
  %0 = linalg.generic #trait_1d_scalar
     ins(%input: tensor<?xf32>)
     outs(%input: tensor<?xf32>) {
      ^bb(%a: f32, %s: f32):
        %0 = arith.mulf %a, %scale  : f32
        linalg.yield %0 : f32
  } -> tensor<?xf32>
  return %0 : tensor<?xf32>
}
"""

Let’s say we wanted to run some passes over this MLIR code.


passes = [
    "--linalg-bufferize",
    "--func-bufferize",
    "--finalizing-bufferize",
    "--convert-linalg-to-affine-loops",
    "--lower-affine",
    "--convert-scf-to-cf",
]

We’ll need our string as a bytes literal.


mlir_bytes = mlir_string.encode()

We can now run passes over our MLIR code like so:


result = cli.apply_passes(mlir_bytes, passes)
print(result)
module {
  func @scale_func(%arg0: memref<?xf32>, %arg1: f32) -> memref<?xf32> {
    %c0 = arith.constant 0 : index
    %0 = memref.dim %arg0, %c0 : memref<?xf32>
    %1 = memref.alloc(%0) : memref<?xf32>
    %2 = memref.dim %arg0, %c0 : memref<?xf32>
    %c0_0 = arith.constant 0 : index
    %c1 = arith.constant 1 : index
    cf.br ^bb1(%c0_0 : index)
  ^bb1(%3: index):  // 2 preds: ^bb0, ^bb2
    %4 = arith.cmpi slt, %3, %2 : index
    cf.cond_br %4, ^bb2, ^bb3
  ^bb2:  // pred: ^bb1
    %5 = memref.load %arg0[%3] : memref<?xf32>
    %6 = arith.mulf %5, %arg1 : f32
    memref.store %6, %1[%3] : memref<?xf32>
    %7 = arith.addi %3, %c1 : index
    cf.br ^bb1(%7 : index)
  ^bb3:  // pred: ^bb1
    return %1 : memref<?xf32>
  }
}


Applying Passes to MLIR Files on Disk

Let’s say that we have some MLIR file on our machine we want to run passes over. We’ll create a temporary file for our example here and use the same MLIR code and passes as above.


temporary_directory = tempfile.TemporaryDirectory()
temporary_filename = os.path.join(temporary_directory.name, "example.mlir")
with open(temporary_filename, 'w') as f:
    f.write(mlir_string)

We can run these passes over our file using the apply_passes method of MlirOptCli to get a string containing the lowered IR:


result = cli.apply_passes(temporary_filename, passes)
print(result)
module {
  func @scale_func(%arg0: memref<?xf32>, %arg1: f32) -> memref<?xf32> {
    %c0 = arith.constant 0 : index
    %0 = memref.dim %arg0, %c0 : memref<?xf32>
    %1 = memref.alloc(%0) : memref<?xf32>
    %2 = memref.dim %arg0, %c0 : memref<?xf32>
    %c0_0 = arith.constant 0 : index
    %c1 = arith.constant 1 : index
    cf.br ^bb1(%c0_0 : index)
  ^bb1(%3: index):  // 2 preds: ^bb0, ^bb2
    %4 = arith.cmpi slt, %3, %2 : index
    cf.cond_br %4, ^bb2, ^bb3
  ^bb2:  // pred: ^bb1
    %5 = memref.load %arg0[%3] : memref<?xf32>
    %6 = arith.mulf %5, %arg1 : f32
    memref.store %6, %1[%3] : memref<?xf32>
    %7 = arith.addi %3, %c1 : index
    cf.br ^bb1(%7 : index)
  ^bb3:  // pred: ^bb1
    return %1 : memref<?xf32>
  }
}


Let’s make sure to clean up the temporary directory we created earlier.


temporary_directory.cleanup()