概要

LLVMプロジェクトは、モジュール化され再利用可能なコンパイラとツールチェイン技術の集合体である。

イリノイ大学の研究プロジェクトとして始まり、任意のプログラミング言語の静的 / 動的コンパイルをサポートできる、SSAベースの近代的なコンパイル戦略を提供することを目的としていた。
その後、LLVMは多くのサブプロジェクトからなるアンブレラプロジェクトに成長して、その多くは、学術研究において広く利用されるだけでなく、様々な商用およびオープンソースプロジェクトによって稼働している。

LLVMの主なサブプロジェクトを、以下に示す。

  • LLVMコアライブラリ
    ソースコードやターゲットに依存しない最新のオプティマイザと、多くの一般的なCPUに対応したコード生成機能を提供する。
    これらのライブラリは、LLVM中間表現(LLVM IR)として知られる指定されたコード表現を中心に構築されている。
    LLVMコアライブラリは文書化されており、LLVMをオプティマイザやコード生成器として使用するための独自の言語の考案(または既存のコンパイラの移植)が容易である。

  • Clang
    LLVMネイティブのC / C++ / Objective-Cコンパイラにおいて、速いコンパイル、非常に有用なエラーと警告メッセージ、優れたソースレベルツールを構築するためのプラットフォームを提供することを目的としている。
    Clang Static Analyzerとclang-tidyは、コードのバグを自動的に見つけるツールであり、C / C++コードを解析するライブラリとしてClangフロントエンドを使用して構築できる。

  • libc++およびlibc++ ABIプロジェクト
    libc++およびlibc++ ABIプロジェクトは、LLVMプロジェクトの重要な部分を構成している。
    これらは、C++標準ライブラリの実装を提供することを目的としている。

    libc++は、C++標準ライブラリの実装そのものを指す。
    このプロジェクトは、C++ 11やC++ 14といった比較的新しい規格を含む、C++言語標準に完全に準拠することを目指している。
    同時に、高性能な実装を提供することも重視しており、効率的なコード生成と実行時のパフォーマンスの両立を図っている。

    一方、libc++ ABIは、Application Binary Interfaceの実装を担当する。
    ABIは、コンパイルされたプログラムの各部分が互いにどのように相互作用するかを定義する低レベルの仕様である。

    libc++ ABIは、libc++と密接に連携して動作し、異なるコンパイラや異なるバージョンのライブラリ間での互換性を確保する重要な役割を果たす。

    これらのプロジェクトの主な目標には、ポータビリティの向上、異なるプラットフォーム間での一貫性の確保、そして最新のC++機能のサポートが含まれる。
    また、オープンソースコミュニティの貢献を積極的に受け入れ、継続的な改善と拡張を行っている。

    libc++とlibc++ ABIは、特にClangコンパイラと組み合わせて使用されることが多いが、他のコンパイラでも利用可能である。
    これらのプロジェクトは、C++開発者に信頼性の高い、最新の標準に準拠したツールセットを提供することにより、C++エコシステム全体の発展に貢献している。

  • compiler-rtプロジェクト
    compiler-rtは、LLVMプロジェクトの重要なサブプロジェクトの1つである。
    その主な目的は、コンパイラがターゲットマシン上で効率的に動作するために必要な低レベルのサポート機能を提供することである。

    compiler-rtは、__fixunsdfdi等の低レベルコードジェネレータサポートルーチンや、
    ターゲットがコアIR操作を実装するための短いネイティブ命令列を持っていない場合に発生するその他のコールの高度に調整された実装を提供している。
    これらのルーチンは、特定のハードウェアアーキテクチャで直接サポートされていない操作を実行するために使用される。
    例えば、64ビット整数演算が32ビットプロセッサ上で必要な場合、compiler-rtがその実装を提供する。

    また、compiler-rtは様々なサニタイザと呼ばれる動的テストツールのランタイムライブラリも実装している。
    これらのツールは、メモリエラー、データ競合、未定義動作等のバグを検出するのに役立つ。

    また、AddressSanitizer、ThreadSanitizer、MemorySanitizer、DataFlowSanitizer等の動的テストツールのランタイムライブラリの実装を提供しており、
    これらはプログラムの実行時に問題を検出し報告する。

    compiler-rtの重要な特徴の1つは、その移植性である。
    様々なアーキテクチャとOSに対応するように設計されており、LLVMベースのコンパイラがさまざまなプラットフォームで効率的に動作することを可能にしている。

    さらに、compiler-rtはプロファイリングやコードカバレッジツールのサポートも提供している。
    これらは、プログラムのパフォーマンス分析やテストの品質向上に役立つ。

    compiler-rtは非常に最適化された実装を提供することにより、生成されるコードの効率を高める。
    これは特に、組み込みシステムや高性能コンピューティング等、パフォーマンスが重要な分野で重要である。

    compiler-rtプロジェクトは、LLVMエコシステムの中で重要な役割を果たしており、高品質で効率的なコード生成を可能にする基盤となっている。

  • MLIRサブプロジェクト
    MLIR (Multi-Level Intermediate Representation) は、LLVMプロジェクトの一部として開発された革新的なコンパイラインフラストラクチャである。
    再利用可能で拡張性のあるコンパイラ基盤を構築するための新しいアプローチである。
    MLIRは、ソフトウェアの断片化の対処、異種ハードウェアに対するコンパイルの改善、ドメイン固有のコンパイラを構築するコストを大幅に削減する等、既存のコンパイラの接続を支援することを目的としている。

    MLIRの特徴的な点は、その柔軟性と拡張性にある。
    従来のコンパイラインフラストラクチャとは異なり、MLIRは単一の固定された中間表現 (IR) を使用するのではなく、多様なレベルの抽象化を表現できる枠組みを提供する。
    これにより、高レベルの言語構造から低レベルのハードウェア固有の命令まで、幅広い抽象化レベルを扱うことができる。

    この柔軟性により、MLIRは様々な用途に適用可能である。
    例えば、機械学習フレームワークの最適化、ハードウェアアクセラレータ向けのコード生成、ドメイン固有言語 (DSL) の実装等に活用されている。

    MLIRの重要な概念の1つに "方言 (Dialect)" がある。
    これは特定のドメインや抽象化レベルに特化したIRの集合で、開発者は自身のニーズに合わせて新しい方言を定義できる。
    この機能により、MLIRは様々な分野やハードウェアに対して高度にカスタマイズ可能となっている。

    また、MLIRは既存のコンパイラインフラストラクチャとの統合も容易である。
    LLVM、GCC、カスタムバックエンドとも連携できるよう設計されている。
    これにより、既存のツールチェーンを活用しつつ、新しい最適化や変換を導入することが可能になる。

    MLIRプロジェクトは活発に開発が進められており、コンパイラ技術の未来を形作る重要な役割を果たしている。
    特に、異種コンピューティングの時代において、MLIRの柔軟性と拡張性は非常に価値があると考えられている。

  • OpenMPサブプロジェクト
    ClangのOpenMP実装で使用するためのOpenMPランタイムを提供する。

  • pollyプロジェクト
    多面体モデルを用いて、自動並列化やベクトル化だけでなく、キャッシュローカリティ最適化のスイートも実装している。

  • libclcプロジェクト
    OpenCL標準ライブラリの実装を目的としている。

  • KLEEプロジェクト
    KLEEプロジェクトは、LLVMのサブプロジェクトとして開発された強力なシンボリック実行エンジンであり、
    ソフトウェアの品質と信頼性を向上させるための強力なツールである。

    その名前は、"Kleene Symbolic Execution Engine" に由来している。

    KLEEの主な目的は、プログラムの自動テストと検証を行うことである。
    これを実現するために、KLEEはプログラムのすべての可能な実行パスを探索し、各パスの条件を分析する。
    この過程で、バグや潜在的な問題を特定して、それらを再現するためのテストケースを自動生成する。

    KLEEの動作原理は、プログラムの入力をシンボリック (抽象的) な値として扱うことにある。
    これにより、具体的な入力値を使用する通常のテストよりも、はるかに広範囲のプログラム動作を分析することができる。

    KLEEの特筆すべき機能として、高カバレッジのテストケース生成がある。
    KLEEはバグを発見した時、KLEEはそのバグを再現するための具体的な入力値を提供する。
    これは開発者にとって非常に有用であり、バグの修正と検証を容易することが可能である。

    定理証明器を用いてプログラムの動的パスの評価を行い、バグの発見や関数の性質を証明する"記号的仮想機械"を実装している。

    また、KLEEは単にバグを見つけるだけでなく、プログラムの正確性を証明するためにも使用できる。
    特定の条件下でプログラムが正しく動作することを形式的に検証することが可能である。

    KLEEの応用範囲は広く、システムソフトウェア、組み込みシステム、セキュリティ重視のアプリケーション等、高い信頼性が要求される分野で特に有効である。

    ただし、KLEEの使用には一定の必要であり、また、複雑なプログラムや大規模なコードベースに対しては計算コストが高くなる可能性がある。

  • LLDプロジェクト
    LLD (LLVM Linker) は、indeed、LLVMプロジェクトの一部として開発されているリンカである。
    その主な目的は、既存のシステムリンカのドロップイン置き換えで、高速な代替品となることである。
    モジュール性、柔軟性、そして何よりも速度に重点を置いて設計されています。

    LLDの特徴の1つは、その速度である。
    従来のリンカと比較して、LLDは非常に高速に動作する。
    これは特に大規模なプロジェクトや頻繁な再コンパイルが必要な開発環境で重要である。

    また、LLDはクロスプラットフォーム対応を強く意識しており、
    Windows、MacOS、Linux等の主要なOSに対応しており、各プラットフォーム固有のオブジェクトファイル形式やバイナリ形式を扱うことができる。

    さらに、LLDはLLVMのエコシステムと緊密に統合されており、
    LLVMの他のツールやライブラリとシームレスに連携することができ、効率的なツールチェーンの構築が可能になる。

    LLDの設計は、モジュール性を重視しており、新しいターゲットや機能の追加が容易になり、メンテナンス性も向上している。

    LLDプロジェクトはLLVMエコシステムの重要な構成要素として、高速で効率的なリンク処理を提供することを目指している。

  • BOLTプロジェクト
    BOLT (Binary Optimization and Layout Tool) は、ポストリンクオプティマイザであり、実行プロファイルを利用してコードレイアウトを最適化することで性能向上を図る。

    BOLTの主な特徴は、既にコンパイルされたバイナリに対して最適化を行うことである。
    これにより、コンパイル時には予測できなかった実行時の挙動を考慮した最適化が可能になる。

    最適化のプロセスを以下に示す。
    (サンプリングプロファイラで収集した実行プロファイルをもとに、ソフトウェアのコードレイアウトを最適化することにより、高速化を実現する)
    1. まず、対象のバイナリを実行環境で動作させ、サンプリングプロファイラを使用して実行プロファイルを収集する。
    2. 次に、BOLTがこのプロファイル情報を解析して、頻繁に実行される関数やコードパスを特定する。
    3. そして、これらの情報をもとに、ホットなコードパスを近接して配置したり、コールドなコードを別の場所に移動したりすることにより、キャッシュの効率性を向上させる。

    BOLTのメリットとして、コンパイラの最適化とは独立して動作するため、既存のビルドプロセスを大きく変更することなく適用できる点がである。
    また、様々なアーキテクチャやコンパイラに対応しているため、幅広い環境で利用可能である。

    実際の性能改善効果は、アプリケーションの特性やワークロードによって異なるが、大規模なサーバアプリケーション等では数パーセントから10%以上の速度向上が報告されている。

    大規模なソフトウェアシステムの性能向上を目指す場合は、特に有用なツールである。



LLVMのインストール

パッケージ管理システムからインストール

sudo zypper install llvm llvm-gold llvm-polly clang lldb lld


ソースコードからインストール

LLVMのインストールに必要なライブラリをインストールする。

# SUSE
sudo zypper install git ncurses-devel isl-devel xz-devel libedit-devel libxml2-devel libbsd-devel lua53 lua53-devel \
                    python3 python3-devel python3-pyaml python3-Pygments                                \  # Python 3.6を使用する場合
                    python311 python311-devel python311-PyYAML python311-Pygments                       \  # Python 3.11を使用する場合
                    doxygen                                                                                # ドキュメントをビルドする場合


LLVMのGithubから、LLVMのソースコードをダウンロードする。
ダウンロードするファイルは、llvm-project-<バージョン>.src.tar.xzである。

ダウンロードしたLLVMのソースコードを解凍する。

tar xf llvm-project-<バージョン>.src.tar.xz


LLVMのビルドディレクトリを作成する。

cd llvm-project
mkdir build && cd build


LLVMをビルドおよびインストールする。

cmake -G "Unix Makefiles" \
      -DCMAKE_C_COMPILER=<GCC 8以降のgccコンパイラのパス> -DCMAKE_CXX_COMPILER=<GCC 8以降のg++コンパイラのパス> \
      -DLLVM_TARGETS_TO_BUILD="X86;ARM;AArch64;AVR;MSP430" \  # x86, x86-64, ARM, AArch64, MSP430, AVR向けのLLVMをビルドする場合(指定しない場合は全てのアーキテクチャ)
      -DCMAKE_BUILD_TYPE=Release                      \
      -DCMAKE_INSTALL_PREFIX=<LLVMのインストールディレクトリ> \
      -DLLVM_ENABLE_RTTI=ON                           \
      -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra;flang;libclc;lld;lldb;mlir;openmp;polly" \
      -DLLVM_ENABLE_RUNTIMES="compiler-rt;libc;libcxx;libcxxabi;libunwind;openmp" \
      ../llvm

make -j $(nproc)
make install

# または

cmake -G Ninja \
      -DCMAKE_C_COMPILER=<GCC 8以降のgccコンパイラのパス> -DCMAKE_CXX_COMPILER=<GCC 8以降のg++コンパイラのパス> \
      -DLLVM_TARGETS_TO_BUILD="X86;AArch64" \  # x86, x86-64, AArch64向けのLLVMをビルドする場合(デフォルトは全てのアーキテクチャ)
      -DCMAKE_BUILD_TYPE=Release                      \
      -DCMAKE_INSTALL_PREFIX=<LLVMのインストールディレクトリ> \
      -DLLVM_ENABLE_RTTI=ON                           \
      -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra;flang;libclc;lld;lldb;mlir;polly" \
      -DLLVM_ENABLE_RUNTIMES="compiler-rt;libc;libcxx;libcxxabi;libunwind;openmp" \
      ../llvm

ninja -j $(nproc)
ninja install


ビルド向けオプションは、以下の通りである。

  • LLVM_TARGETS_TO_BUILD
    セミコロンで区切られたリストで、どのターゲットをビルドしてLLVMにリンクするかを制御する。
    デフォルト値はLLVM_ALL_TARGETSとして定義され、ツリー外のターゲットを含むように設定できる。
    デフォルト値は、ARMAArch64AMDGPUAVRBPFHexagonLanaiMipsMSP430NVPTXPowerPCRISCVSparcSystemZWebAssemblyX86XCoreである。

    このオプションの値をhostと指定すると、ホストアーキテクチャのみがコンパイルされる。
    例えば、x86ホストPCでX86を指定するのと同じである。

  • PYTHON_EXECUTABLE
    Pythonへのパスを渡すことで、CMakeに特定のPythonのバージョンを使用する。
    デフォルトでは環境変数PATHにあるPythonが使用される。

  • LLVM_TARGETS_TO_BUILD
    ビルドするターゲットを選択する。
    これは、LLVMにどのターゲットをリンクするかを制御するセミコロンで区切られたリストである。
    デフォルトは、LLVM_ALL_TARGETSとして定義されている。
    デフォルトでは、以下のターゲットが含まれる。
    AArch64, AMDGPU, ARM, AVR, BPF, Hexagon, Lanai, Mips, MSP430, NVPTX, PowerPC, RISCV, Sparc, SystemZ, WebAssembly, X86, XCore

  • LLVM_ENABLE_DOXYGEN
    ソースコードからdoxygenベースのドキュメントをビルドする。
    これは多くの出力を生成するため、デフォルトでは無効になっている。

  • LLVM_ENABLE_PROJECTS
    他のLLVMサブプロジェクトの内、どのプロジェクトを追加でビルドするかをセミコロンで区切ったリストで指定する。
    デフォルトでは空のリストである。
    ビルドできるプロジェクトは、以下の通りである。
    clang、clang-tools-extra、compiler-rt、cross-project-tests、flang、libc、libclc、libcxx、libcxxabi、libunwind、lld、lldb、mlir、openmp、polly、pstl

  • LLVM_ENABLE_SPHINX
    Sphinxベースのドキュメントをビルドする。
    これは多くの出力を生成するため、デフォルトでは無効になっている。
    Sphinx 1.5以降を推奨する。

  • LLVM_BUILD_LLVM_DYLIB
    libLLVM.soを生成する。
    このライブラリには、LLVMコンポーネントのデフォルトセットが含まれており、LLVM_DYLIB_COMPONENTSでオーバーライドすることができる。
    デフォルトでは、ほとんどのLLVMが含まれており、tools/llvm-shlib/CMakelists.txtで定義されている。
    このオプションはWindowsでは使用できない。

  • LLVM_OPTIMIZED_TABLEGEN
    リリーステーブル生成器を構築する。


LLVMのインストール完了後、~/.profileファイル等に環境変数を設定する。

 # ~/.profileファイル
 
 export PATH="/<LLVMのインストールディレクトリ>/bin:$PATH"
 export LLVM_INSTALL_DIR="<LLVMのインストールディレクトリ>"
 
 export LDFLAGS="-L/<LLVMのインストールディレクトリ>/lib"       # 不要の可能性あり
 export CPPFLAGS="-I/<LLVMのインストールディレクトリ>/include"  # 不要の可能性あり



LLVMのクロスコンパイル

LLVMの実行ファイルやライブラリにおいて、ビルドされるプラットフォームとは異なるプラットフォームでホストするためにインストールすることができる。
クロスコンパイル向けのビルドファイルを生成するために、-DCMAKE_TOOLCHAIN_FILEオプションを、LLVMのインストール時に使用するコンパイラフラグや変数を定義することができる。

AArch64

以下の例では、AArch64をターゲットとしたLLVMをクロスビルドおよびインストールしている。

mkdir build && cd build

cmake -G Ninja                                                       \
      -DCMAKE_BUILD_TYPE=Release                                     \
      -DCMAKE_INSTALL_PREFIX=<LLVMクロスツールチェーンのインストールディレクトリ> \
      -DCMAKE_C_COMPILER=/<クロスコンパイラのパス  例: aarch64-unknown-linux-gnu-gcc>   \  # CMakeツールチェーンファイルを使用しない場合
      -DCMAKE_CXX_COMPILER=/<クロスコンパイラのパス  例: aarch64-unknown-linux-gnu-g++> \  # CMakeツールチェーンファイルを使用しない場合
      -DCMAKE_CXX_FLAGS='-march=armv8-a -mtune=cortex-a53'                        \  # CMakeツールチェーンファイルを使用しない場合 (左の例は、PinePhoneの場合)
      -DCMAKE_TOOLCHAIN_FILE=<CMakeツールチェーンファイルのパス>                         \  # CMakeツールチェーンファイルを使用する場合
      -DLLVM_NATIVE_TOOL_DIR=/<ホスト向けLLVMのインストールディレクトリ>/bin                 \
      -DCMAKE_CROSSCOMPILING=1               \
      -DLLVM_TARGET_ARCH="AArch64"           \
      -DLLVM_TARGETS_TO_BUILD="AArch64"      \
      -DLLVM_BUILD_RUNTIME=Off               \
      -DLLVM_INCLUDE_TESTS=Off               \
      -DLLVM_INCLUDE_EXAMPLES=Off            \
      -DLLVM_ENABLE_BACKTRACES=Off           \
      -DLLVM_DEFAULT_TARGET_TRIPLE=aarch64-unknown-linux-gnu         \
      -DLLVM_TABLEGEN=/<ホスト向けLLVMのインストールディレクトリ>/bin/llvm-tblgen     \
      -DCLANG_TABLEGEN=/<ホスト向けLLVMのインストールディレクトリ>/bin/clang-tblgen   \
      -DCMAKE_LIBRARY_ARCHITECTURE=/<ターゲットのシステムルートのパス>/lib          \
      -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra;compiler-rt;lld;lldb" \
      -DLLVM_INSTALL_TOOLCHAIN_ONLY=On \
      -DLLDB_ENABLE_PYTHON=0           \
      -DLLDB_ENABLE_LIBEDIT=0          \
      -DLLDB_ENABLE_CURSES=0           \
      -DLLVM_BUILD_LLVM_DYLIB=On       \
      -DLLVM_LINK_LLVM_DYLIB=On        \
      -DDEFAULT_SYSROOT=<ターゲットのシステムルートディレクトリ> \
      ../llvm


iOS

以下の例では、iOSをターゲットとしたLLVMをクロスビルドおよびインストールしている。

mkdir build && cd build

cmake -G Ninja "Unix Makefiles"                                                           \
               -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=<LLVMのインストールディレクトリ> \
               -DCMAKE_OSX_ARCHITECTURES="armv7;armv7s;arm64"                             \
               -DCMAKE_TOOLCHAIN_FILE=<PATH_TO_LLVM>/cmake/platforms/iOS.cmake            \
               -DLLVM_BUILD_RUNTIME=Off -DLLVM_INCLUDE_TESTS=Off                          \
               -DLLVM_INCLUDE_EXAMPLES=Off -DLLVM_ENABLE_BACKTRACES=Off                   \
               ../llvm

ninja -j $(nproc)
ninja install