TL; DR
# イメージが大きいので注意!(2GB)
$ docker run -it --rm ghcr.io/syuparn/mlir-hello:main bash
root@032a4b2bb9d6:/mlir-hello# ./build/bin/hello-opt ./test/Hello/print.mlir > print.ll
root@032a4b2bb9d6:/mlir-hello# /lib/llvm-18/bin/lli print.ll
1.000000 2.000000 3.000000
4.000000 5.000000 6.000000
(上記はこちらのプロジェクトをforkして作成)
はじめに
MLIR(Multi-Level Intermediate Representation)は、言語の中間表現や中間表現同士の変換等を定義することができるコンパイラ基盤です。
LLVMプロジェクトの1つとして管理されており、LLVM IRよりASTに近い抽象的な表現(dialect)を定義することも可能です(徐々に抽象度の低い表現へ変換する(lowering)ことでLLVM IRに変換します)。
MLIRについては、こちらの記事が詳しく紹介されています。
そんなMLIRを現在学習中なのですが、実装以前に環境構築でかなり躓いてしまいました。
チュートリアルである Toy
言語の実装では、動かす前にLLVM/MLIRをソースコードからビルドします。
オプションやツール同士のバージョンに気を付ける必要があり、さらに(自業自得ですが)事前にLLVM 17もインストールしていたため1環境がぐちゃぐちゃになってしまいました。
MLIRで自作言語を作る予定でしたが、利用者が上記のようにコンパイラをビルドする必要があるとなるとHello Worldの前に心が折れてしまいそうです。準備不要で、1コマンドでコンパイラを使い始められる方法があれば...
ありました。Dockerコンテナです。
そこで本記事では、ビルド済みMLIR dialectのDockerイメージを作る方法を紹介します。
目標
趣味の自作言語を布教しやすくする(手軽に触ってもらえるようにする)のが目標です。また、開発者(自分)自身も手軽に環境構築できるのが理想です。
- 利用者:Dockerコンテナ
- イメージをpullしてコンテナを起動するだけでコンパイラを実行できる
- 開発者:ローカル環境(Ubuntu)
- apt installだけで依存ツールが揃う
- VSCodeのインテリセンスも効く
代わりに、イメージサイズや動作の効率は不問としました。実際2GBを超えています
バージョン
- LLVM/MLIR 18.1.4
- ローカル環境: Ubuntu 20.04 (on WSL2)
ビルドするプロジェクト
llvm-project のリポジトリと独立している小さなdialectが必要だったので、こちらのHello Dialectをforkして使わせていただきました。
コンテナ内でも、サンプルコードの以下のMLIRをLLVM IRへ変換できるようにします。
func.func @main() {
%0 = "hello.constant"() {value = dense<[[1.000000e+00, 2.000000e+00, 3.000000e+00], [4.000000e+00, 5.000000e+00, 6.000000e+00]]> : tensor<2x3xf64>} : () -> tensor<2x3xf64>
"hello.print"(%0) : (tensor<2x3xf64>) -> ()
return
}
Dialectについての詳細は作者様の解説記事をご覧ください。
必要なツールをインストールする
イメージ化にあたり、llvm-projectからビルドしていた2ツールを別手段で入手します。
Ubuntuのベースイメージを使用すればローカル環境と同じコマンドでイメージも作成できるため、まずはローカル環境の構築について見ていきます。
LLVM, MLIRをaptでインストールする
最新版(執筆時点)のLLVM 18.1.4をインストールします。公式サイトの手順に従いリポジトリを追加します。
debパッケージが
- Ubuntuのバージョン
- LLVMのメジャーバージョン
ごとに異なる点に注意です。
# 例:Ubuntu22.04 / LLVM, MLIR 18の場合
$ wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add -
$ apt-add-repository "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-18 main"
$ apt update
$ apt install -y libllvm-18-ocaml-dev libllvm18 llvm-18 llvm-18-dev llvm-18-doc llvm-18-examples llvm-18-runtime libmlir-18-dev libmlir-18 mlir-18-tools
llvm-litをインストールする
上記ではe2eテストに使用する llvm-lit
がインストールできないため、pip経由で別途インストールします。現状1つ前のパッチバージョン 18.1.3
しか公開されていませんでしたが、互換性は問題ありませんでした。
$ pip install lit==18.1.3
mlir-helloでは llvm-lit
の実行ファイルを ./build/bin
配下に置く必要があるので、突貫工事ですが以下のbashファイルで lit
コマンドを呼び出すようにしています。
#!/bin/bash
lit $@
動作確認
ここまでで、ローカル環境でdialectのビルド、テストがどちらも成功するようになりました。
# dialectのビルド
cd build
# 注意:aptでLLVM, MLIRをインストールしたディレクトリを指定すること!
cmake -G Ninja .. -DLLVM_DIR=/lib/llvm-18/lib/cmake/llvm -DMLIR_DIR=/lib/llvm-18/lib/cmake/mlir
cmake --build . --target hello-opt
# コンパイラの動作確認
$ cd ..
# Hello dialect: MLIR -> LLVM IR
$ ./build/bin/hello-opt ./test/Hello/print.mlir > print.ll
# LLVM IRのインタープリタで実行
$ lli print.ll
1.000000 2.000000 3.000000
4.000000 5.000000 6.000000
# テスト
$ cd build
$ cmake --build . --target check-hello
[0/1] Running the hello regression tests
Testing Time: 0.04s
Total Discovered Tests: 9
Passed: 9 (100.00%)
イメージ化する
イメージのビルドについても、基本的には上記コマンドを Dockerfile
に記載するだけです。
FROM ubuntu:22.04
# cmake ninja-build: ビルドに必要
# software-properties-common: apt-add-repositoryに必要
RUN apt-get update -y && apt-get install -y python3 python3-pip cmake ninja-build curl wget software-properties-common
# LLVM/MLIR 18 のインストール
RUN wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add -
RUN apt-add-repository "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-18 main" && \
apt-get update && \
apt-get install -y libllvm-18-ocaml-dev libllvm18 llvm-18 llvm-18-dev llvm-18-doc llvm-18-examples llvm-18-runtime libmlir-18-dev libmlir-18 mlir-18-tools
# llvm-lit のインストール
RUN pip install lit==18.1.3
# Hello dialectをビルド
WORKDIR /mlir-hello
COPY . .
COPY llvm-lit build/bin/llvm-lit
WORKDIR /mlir-hello/build
RUN cmake -G Ninja .. -DLLVM_DIR=/lib/llvm-18/lib/cmake/llvm -DMLIR_DIR=/lib/llvm-18/lib/cmake/mlir
RUN cmake --build . --target hello-opt
WORKDIR /mlir-hello
apt-add-repository
についてはこちらの記事を参考にさせていただきました。
あとはghcr.ioにpushすれば、利用者は docker run
だけでコンパイラが使用できるようになりました!
# イメージが大きいので注意!(2GB越え)
$ docker run -it --rm ghcr.io/syuparn/mlir-hello:main bash
root@032a4b2bb9d6:/mlir-hello# ./build/bin/hello-opt ./test/Hello/print.mlir > print.ll
root@032a4b2bb9d6:/mlir-hello# /lib/llvm-18/bin/lli print.ll
1.000000 2.000000 3.000000
4.000000 5.000000 6.000000
GitHub Actionsは公式ドキュメントを参考に作成しました。
開発環境の整備
続いて、開発者側のローカル環境を整備します。コンパイルは前章まででできるようになりましたが、VSCode上でclangdのインテリセンスが効かなくなってしまったので数か所対応が必要でした。
clangdのインテリセンスが効くようにする
includeしたヘッダファイルが見つからない問題
aptでインストールしたものとは別のLLVMを参照してしまっていたため、一部の include
でnot foundが発生してしまいました。
clangdにもビルド時と同じコンパイルオプションを認識させるため、compile_flags.txt
をリポジトリのルートフォルダに追加することで解消しました。
-xc++
-Iinclude
-DLLVM_DIR=/lib/llvm-18/lib/cmake/llvm
-DMLIR_DIR=/lib/llvm-18/lib/cmake/mlir
compile_flags.txt
についてはこちらの記事を参考にさせていただきました。
ビルドオプションが認識されずコンパイルエラー扱いになってしまう問題
上記を解消したら、 今度は以下のコンパイルエラーが発生しインテリセンスが効かなくなってしまいました。
Unknown argument: '-fno-lifetime-dse'clang(drv_unknown_argument)
clangdでサポートされていないコンパイルフラグを指定してしまったのが原因のため、 .clangd
で無視する設定を入れることで解消しました。
全てのサブディレクトリに配置する必要がある 点に注意です。
# use for syntax highlight
CompileFlags:
Remove:
- -fno-lifetime-dse
詳細はこちらのissueをご覧ください。
これで、インテリセンスが効くようになり、ローカル環境の開発準備も無事整いました。
おわりに
以上、MLIR dialectをイメージで配布する方法の紹介でした。イメージが重いことを除けば、比較的手軽にコンパイラを使い始められるようになったと思います。マルチステージビルドでdialectのバイナリとLLVM本体だけにすることで軽量化もできるかもしれません。
自分の開発環境も整ったところで、次はいよいよコンパイラを実装していきたいと思います。
-
Learn LLVM 17 を写経していたので、LLVM 17だけ事前にインストールされています。ちなみにこのときもソースコードからビルドしました。 ↩
-
mlir-helloではthird-party配下にllvm-projectがまるごと格納されており、それをビルドして使用しています。 ↩