【ITニュース解説】Compiling LLVM and Running Your First Dummy Pass
2025年09月20日に「Dev.to」が公開したITニュース「Compiling LLVM and Running Your First Dummy Pass」について初心者にもわかりやすく解説しています。
ITニュース概要
LLVMは、コンパイラの仕組みを学べる強力なツールだ。この記事では、LLVMを自分でビルドし、「Hello World」という簡単な処理(パス)を作成・実行する手順を解説する。これにより、コンパイラの内部動作を実践的に理解し、開発の第一歩を踏み出せる。
ITニュース解説
コンパイラがプログラムをどのように動かすのか、その内部の仕組みに興味を持ったことはないだろうか。LLVMは、そのようなコンパイラの深層を学ぶための非常に強力で柔軟なツール群であり、プログラミング言語からGPUドライバーまで、幅広い分野で利用されている。その規模の大きさから、最初の一歩を踏み出すのは難しく感じられるかもしれないが、この記事はLLVMの「パス」という概念を通して、コンパイラの核心部分に触れるための具体的な手順を紹介する。LLVMパスは、コンパイラがプログラムを最適化したり、その構造を解析したりする上での基本的な構成要素だ。独自のパスを作成し、LLVMの中間表現(IR)を操作することで、LLVMがどのように動作するかの核となる概念を実践的に学ぶことができる。この解説は、LLVMをソースコードからビルドし、シンプルな「Hello World」パスを実行するまでの一連の流れを説明し、コンパイラ内部への入り口を示すものとなる。
まず、LLVMを動作させるための環境準備が必要だ。UbuntuやDebianのようなLinuxシステムでは、sudo apt -y install gcc g++ git cmake ccache ninja-build zlib1g-devというコマンドで、C++コンパイラ、ソースコード管理ツール、ビルドツール、ビルド高速化ツールなど、開発に必要な基本的なツールを一括でインストールできる。特にGitはLLVMの巨大なソースコードを入手するために不可欠であり、CcacheはLLVMのビルド時間を大幅に短縮してくれる。
次に、LLVMをソースコードからビルドする手順に進む。これは、LLVMの最新機能を利用したり、その動作をカスタマイズしたりするために重要な作業だ。
まず、git clone https://github.com/llvm/llvm-project.gitコマンドで、LLVMプロジェクトのソースコード全体をGitHubから自分のPCにコピーする。
その後、cd llvm-project/でコピーしたディレクトリに移動し、git checkout -b llvm-17 llvmorg-17.0.1で、特定のバージョン(ここではバージョン17.0.1)のソースコードを基にした新しいブランチに切り替える。これは安定した開発環境を構築するために推奨される。
次に、mkdir buildでビルド用のディレクトリを作成し、cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=TRUE -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DLLVM_ENABLE_PROJECTS=clang -DCMAKE_INSTALL_PREFIX=/opt/llvm -B build -S llvmというコマンドでビルドの設定を行う。このコマンドでは、高速なビルドシステムであるNinjaを使用し、リリース版としてビルドすること、そして共有ライブラリとしてビルドすること(これは後で作成するパスを問題なく実行するために重要だ)などを指定している。また、clangコンパイラを含めてビルドし、最終的に/opt/llvmにインストールする設定も含まれている。
設定が完了したら、cmake --build build -j$(nproc)コマンドでLLVMのビルドを開始する。-j$(nproc)オプションは、CPUのコア数を最大限に活用してビルドを並行処理し、時間を短縮する。
ビルドが成功したかを確認するため、cmake --build build --target check-all -j$(nproc)を実行する。これはLLVMが提供する膨大なテストスイートを実行し、ビルドされたコンポーネントが正しく動作するかを検証する。すべてのテストがパスすれば、ビルドは成功だ。
最後に、cmake --install buildコマンドを実行すると、ビルドされたLLVMとClangが/opt/llvmにインストールされる。これで、動作するLLVMとClangの環境が整ったことになる。
次に、独自の「Hello World」LLVMパスを作成する。このパスは、プログラムの動作自体を変更するものではなく、実行されるたびにメッセージを表示するだけの非常にシンプルなものだ。
hellopass.cppというC++のソースファイルを作成し、コードを記述する。このコードには、LLVMの関数やパス管理の機能を利用するためのヘッダファイル群が含まれる。
パスの核となるのは、HelloWorldPassという構造体内のrunメソッドだ。このメソッドが、パスが実行する具体的なロジックを記述する場所になる。ここでは、errs() << "Hello from: " << F.getName() << "\n";という行で、パスが処理している関数の名前とともに「Hello from: 」というメッセージを標準エラー出力に表示する。PreservedAnalyses::all()は、このパスがプログラムの解析結果を一切変更しないことをLLVMに伝える。
LLVMがこのパスを認識し、ロードできるようにするためには、「パス登録」という手順が必要だ。コードの後半部分にある関数は、LLVMがこのパスをプラグインとして動的にロードし、特定の名前(例:「hello-world」)が指定されたときに、作成したHelloWorldPassをLLVMの処理パイプラインに追加するように登録する役割を担っている。現時点では、パスの「具体的な処理ロジック」と「LLVMへの登録メカニズム」という二つの主要な部分があることを理解しておくと良いだろう。
続いて、作成したパスをコンパイルし、LLVMがロードできる共有ライブラリとしてビルドする。
パスのビルドには、再度CMakeを利用する。CMakeLists.txtというファイルを作成し、そこにビルド設定を記述する。
この設定ファイルでは、まずCMakeの最小バージョンやプロジェクト名を指定する。次に、find_package(LLVM REQUIRED CONFIG)というコマンドで、システムにインストールされているLLVMのビルド設定を見つけ出し、パスのコンパイルに必要なLLVMのヘッダファイルや定義情報を取得する。
add_library(HelloPass MODULE hellopass.cpp)は、hellopass.cppからHelloPassという名前の共有ライブラリ(Linuxでは.soファイル)を作成することをCMakeに指示する。ここでMODULEキーワードを使うことで、このライブラリが実行時に動的にロードされるプラグインであることを示している。また、set_target_propertiesでC++17標準に準拠してコンパイルすることを保証する。
このCMakeLists.txtを使ってビルドを実行する。まず、mkdir build && cd buildでビルドディレクトリを作成し、その中に移動する。
次に、cmake -DLLVM_DIR=/opt/llvm/lib/cmake/llvm -DCMAKE_CXX_COMPILER='clang++' ..を実行して、パスのビルド設定を行う。-DLLVM_DIRオプションは、以前インストールしたLLVMのCMake設定ファイルがある場所をCMakeに教えるために重要だ。また、コンパイラとしてClang++を使用することを明示的に指定している。
最後に、cmake --build .でパスのコンパイルを実行すると、libHelloPass.soという名前の共有ライブラリが、ビルドディレクトリ内に生成される。
いよいよ、作成したパスを実行する段階だ。
まず、パスの動作を確認するための簡単なC++プログラムを用意する。例えば、test.ccというファイルにint add(int a, int b) { return a + b; }のような関数を含むだけのシンプルなコードを記述する。
このC++コードを、LLVMパスが直接処理できる形式であるLLVMの中間表現(IR)にコンパイルする。clang++ -S -emit-llvm test.cc -o test.llコマンドを使用する。/opt/llvm/bin/clang++は、先ほどインストールしたClang++コンパイラだ。-S -emit-llvmオプションは、C++ソースコードをLLVM IRのテキスト形式で出力するように指示し、結果がtest.llというファイルに保存される。
最後に、LLVMの最適化ツールであるoptを使って、作成したパスをtest.llファイルに対して実行する。
opt -load-pass-plugin=./libHelloPass.so -passes=hello-world -disable-output < test.llというコマンドを実行する。
/opt/llvm/bin/optは、インストールしたoptツールへのパスだ。
-load-pass-plugin=./libHelloPass.soは、先ほどビルドした共有ライブラリlibHelloPass.soをoptツールに読み込ませるためのオプションだ。
-passes=hello-worldは、読み込んだプラグインの中から「hello-world」という名前で登録したパスを実行するように指定する。
-disable-outputは、optツールがIRを処理した後の結果をファイルに出力しないように指示する。今回のパスはIRを変更しないため、このオプションで十分だ。
< test.llは、test.llファイルをoptツールの入力として与えることを意味する。
このコマンドを実行すると、画面上にHello from: addというメッセージが表示されるはずだ。これは、作成した「Hello World」パスが、test.llファイルに含まれるadd関数に対して正常に実行されたことを示している。
これで、LLVMをソースコードからビルドし、自身の最初のLLVMパスを開発・実行するという一連のプロセスを完了したことになる。この「Hello World」の経験は、コンパイラ開発の世界への貴重な第一歩だ。ここからさらに、プログラムの制御フローを解析したり、命令を最適化したり、あるいはLLVM IR自体をより複雑に変換したりといった、高度なパスを作成していくための基礎ができたと言えるだろう。コンパイラの奥深い世界を探求するためのゲートウェイが、今開かれたのだ。