Notes on Using the MLIR C API in Swift
For curiosity's sake, I decided I want to play with MLIR's C API with Swift. I spent quite some time to get a skeleton project up and running on my Mac. Here's my notes for future reference. (If you find this useful, I'd be curious to know what you're working on!).
Modern LLVM comes shipped with MLIR. At the time of writing, all I had to do to
get it is brew install llvm
. If you used the default Homebrew installation
options, you'll find libMLIR.dylib
and friends under
/opt/homebrew/opt/llvm/
. Building MLIR following the
instructions on the website is also
fairly straightforward.
You'll want llvm-config
from your version of LLVM to be in your path. For the
Homebrew-installed version, you want /opt/homebrew/opt/llvm/bin/
to be one of
the place the shell looks.
Now, it's time to make the project. With CMake, of course. Because I couldn't figure out how to tell SwiftPM to link the right dylib :) But worry not, CMake ain't that bad.
Like with SwiftPM, we want to make a module for the MLIR C API. I call the module
cmlir
. Make a directory with that name, and create 2 text files:
First, module.modulemap
:
module cmlir [system] {
header "shim.h"
export *
}
Second, shim.h
:
#include <mlir-c/IR.h>
Amazing.
Let's assume we want to have a Swift library that uses cmlir
. And a executable
that depends on the library. You can organize the Swift source files for these
as you like (yay CMake!).
The sample library has one file, lib.swift
:
import cmlir
public func makeAContext() -> MlirContext {
mlirContextCreate()
}
The sample app is just a main.swift
:
import MLIRSwift
print(makeContext())
... as you can see, through these targets, we are expecting to properly execute some code from MLIR.
All that's left is to build all these stuff. AKA, the hard part! But the
CMakeLists.txt
really isn't that bad. I'll just leave it here with comments:
cmake_minimum_required(VERSION 3.22)
# Note we include "C" here, without it there'd be a build error ๐คท
project(swift-mlir LANGUAGES C CXX Swift)
# This is where llvm-config comes to play
find_package(MLIR REQUIRED CONFIG)
include_directories(${MLIR_INCLUDE_DIRS})
# Include our modulemap
include_directories(cmlir)
# I can't believe this is all it takes to make a Swift dylib!
add_library(MLIRSwift SHARED lib.swift)
# Wasted a lot of time on figuring out the right library to link T-T
target_link_libraries(MLIRSwift PRIVATE MLIRCAPIIR)
# Nothing special here
add_executable(myapp main.swift)
target_link_libraries(myapp PRIVATE MLIRSwift)
And there you have it. Here's the file structure in the end:
.
โโโ CMakeLists.txt
โโโ cmlir
โ โโโ module.modulemap
โ โโโ shim.h
โโโ lib.swift
โโโ main.swift
For completeness, I'll also include commands that produces the final executables. It's just the simplest cmake commands. But it may not be obvious for Swift programmers:
make build # make a bulid direcory anywhere, make sure you .gitignore it if necessary
cd build
cmake -G Ninja ..
cmake --build .
With this sample project, running build/myapp
should get you this output:
MlirContext(ptr: Optional(0x0000600001784180))
And that's just exciting, isn't it?