LLVMコア -- コンパイラシステムの心臓部

編集

はじめに

編集

LLVMは、コンパイラシステムのミドルウェアとして、中間表現コード(LLVM IR)を生成し、最適化、ターゲットマシンに合わせたコード生成、実行といった役割を担っています。LLVMコアは、これらの機能を支える基盤であり、LLVMコンパイラシステムの心臓部とも言えます。

本章では、LLVMコアの構成要素と各要素の役割について詳しく説明します。また、各要素の詳細な仕組みや動作についても解説します。

LLVM IR

編集

LLVM IR(LLVM Intermediate Representation)は、LLVM(Low Level Virtual Machine)コンパイラフレームワークで使用される中間言語の一種です。LLVMは、様々なプログラミング言語(CC++RustKotlin NativeJuliaCrystalZigなど)のコンパイラや最適化ツールの基盤として使われます。LLVM IRは、これらの言語のソースコードをコンパイルした後の中間形式の表現です。 LLVM IRは、低レベルなアセンブリ言語に近い形式でありながら、高レベルな抽象化を持っています。LLVM IRは、構造化されたプログラム表現を提供し、機械による解析や最適化が行いやすいように設計されています。LLVMの柔軟性と性能の高さは、その中間表現であるLLVM IRの効果的な設計に基づいています。

LLVM IRの特徴は次のとおりです:

プラットフォーム中立性
LLVM IRは、プラットフォームに依存しない中間表現であり、様々なアーキテクチャやOSに対応しています。
型付け
LLVM IRは型付き言語です。つまり、変数や関数に型が付与され、型の整合性が検証されます。
SSA形式
静的単一代入形式(Static Single Assignment、SSA)形式で表現されます。これは、変数が唯一の代入箇所でしか変更されないことを保証する形式で、多くの最適化手法に適しています。
中間表現の最適化
LLVM IRは、高度な最適化を可能にする設計がなされています。このため、コンパイラが生成する様々な最適化を適用することができます。
直観的な構造
LLVM IRは人間にとっても比較的読みやすい構造を持っています。これは、デバッグや解析を容易にするために重要です。

LLVM IRは、以下の要素で構成されています。

命令
演算、メモリアクセス、分岐、関数呼び出しなどの操作を表します。
データ型
整数、浮動小数点、ポインタ、構造体、関数などのデータ型を表します。
グローバル変数
プログラム全体で共有される変数を表します。
関数
プログラムのロジックを表します。

LLVMは、IRに対して以下の最適化を実行します。

定数折りたたみ
定数式を計算し、結果を定数で置き換えます。
共通部分式除去
同じ式が複数回使用されている場合、一度だけ計算し、結果を再利用します。
デッドコード除去
使用されないコードを削除します。
ループ最適化
ループの反復回数を削減したり、ループ内の命令を並べ替えたりします。

クイックツアー

編集

ここでは、LLVM IRの基本的な構文や特徴についてのクイックツアーを提供します。

  1. LLVM IRの基本的な構文
    LLVM IRは、アセンブリ言語に似た文法を持ちます。以下は、基本的な構文の例です:
    define i32 @add(i32 %a, i32 %b) {
      %result = add i32 %a, %b
      ret i32 %result
    }
    
    • define:関数定義の始まりを示すキーワード。
    • i32:整数型(32ビット)を示す型。
    • @add:関数名。
    • %a%b:引数。
    • %result:ローカル変数。
    • add:加算命令。
    • ret:関数からの戻り。
  2. 型付け
    LLVM IRは、強力な型システムを持っています。たとえば、整数、浮動小数点数、ポインタ、ベクトルなどのさまざまな型がサポートされています。また、ポインタ型は明示的なアドレス空間を持ちます。
  3. 静的単一代入形式(SSA)
    LLVM IRは、静的単一代入形式(SSA)を採用しています。これは、変数が唯一の代入箇所でしか変更されないことを保証する形式です。例えば、以下のコードはSSA形式で表現されます:
    %1 = add i32 %a, %b
    %2 = mul i32 %1, 2
    
  4. 最適化
    LLVM IRは、多くの最適化を適用できるよう設計されています。変数の削除やコードの変形など、様々な最適化が可能です。
  5. データフローグラフ
    LLVM IRは、データフローグラフの形式で表現されます。このため、コードの解析や変換が容易に行えます。
  6. LLVMツール
    LLVMには、LLVM IRを生成したり操作したりするための様々なツールが含まれています。例えば、clangコンパイラは、ソースコードをLLVM IRに変換することができます。また、optツールは、LLVM IR上で様々な最適化を行うことができます。

これはLLVM IRのクイックツアーの概要です。LLVM IRは、LLVMフレームワークの中核を成す部分であり、その柔軟性と性能は、この中間表現の効果的な設計によるものです。

LLVM IR の文法

編集

LLVM IRの文法は、アセンブリ言語に似た構文を持ちます。以下に、LLVM IRの基本的な文法要素を示します。

  1. プログラム構造
    LLVM IRプログラムは、グローバルな定義と関数定義で構成されます。
    • グローバルな定義: グローバルな変数や定数などの定義を含みます。
    • 関数定義: 関数の定義が含まれます。
  2. 関数定義
    関数定義は、次のような構造を持ちます:
    define [linkage] [visibility] [dll_storage_class] [cconv] [ret attrs] [unnamed_addr] [addr_space] [section "name"] [comdat [($name)]] [prefix] [prologue] [personality] [cstyle attrs] [gc] [prefix] [prologue] [personality] [cstyle attrs] [gc] [prefix] [prologue] [personality] [cstyle attrs] [gc] <result> <function_name>(<arg_type> <arg_name>, ...) [fn attrs] [section "name"] [align N] [gc] [prefix] [prologue] [personality] [cstyle attrs] [gc] {
      ; 関数の本体
    }
    
    • define: 関数の定義を開始するキーワード。
    • linkage: リンケージ指定子(オプション)。
    • visibility: 可視性指定子(オプション)。
    • dll_storage_class: DLLストレージクラス(オプション)。
    • cconv: 呼び出し規約(オプション)。
    • ret attrs: 戻り値の属性(オプション)。
    • unnamed_addr: 名前なしアドレス(オプション)。
    • addr_space: アドレス空間(オプション)。
    • section "name": セクション名(オプション)。
    • comdat: コムデータ指定(オプション)。
    • prefix: プリフィックス(オプション)。
    • prologue: プロローグ(オプション)。
    • personality: 例外処理のパーソナリティ関数(オプション)。
    • cstyle attrs: Cスタイルの属性(オプション)。
    • gc: ガベージコレクタ指定(オプション)。
    • result: 戻り値の型。
    • function_name: 関数名。
    • arg_type: 引数の型。
    • arg_name: 引数名。
    • fn attrs: 関数の属性(オプション)。
    • align N: アラインメント指定(オプション)。
  3. 命令
    関数の本体は、命令で構成されます。命令は、次のような形式を持ちます:
    <result> = <opcode> [fast-math-flags] [fn attrs] [operand1, operand2, ...]
    
    • result: 命令の結果の格納先。
    • opcode: 命令のオペコード(加算、乗算、メモリアクセスなど)。
    • fast-math-flags: 高速演算フラグ(オプション)。
    • fn attrs: 命令の属性(オプション)。
    • operand1, operand2, ...: 命令のオペランド。
  4. コメント
    コメントは、;で始まります。
    ; これはコメントです
    

以下は、LLVM IRでよく使用される命令の一覧を表組みで示したものです。

LLVM IRでよく使用される命令
命令 意味
add 加算 %result = add i32 %a, %b
sub 減算 %result = sub i32 %a, %b
mul 乗算 %result = mul i32 %a, %b
sdiv 符号付き除算 %result = sdiv i32 %a, %b
udiv 符号なし除算 %result = udiv i32 %a, %b
icmp 整数比較 %cmp = icmp slt i32 %a, %b
fcmp 浮動小数点数比較 %cmp = fcmp olt float %a, %b
and ビット論理積 %result = and i32 %a, %b
or ビット論理和 %result = or i32 %a, %b
xor ビット排他的論理和 %result = xor i32 %a, %b
shl 左シフト %result = shl i32 %a, %b
lshr 論理右シフト %result = lshr i32 %a, %b
ashr 算術右シフト %result = ashr i32 %a, %b
alloca メモリの割り当て %ptr = alloca i32
load メモリからの読み込み %val = load i32, i32* %ptr
store メモリへの書き込み store i32 %val, i32* %ptr
getelementptr ポインタの算術演算 %ptr = getelementptr i32, i32* %arr, i32 %idx
call 関数呼び出し call void @foo(i32 %arg1, i32 %arg2)
ret 関数からの戻り ret i32 %result
br 分岐 br label %label
switch スイッチ文 switch i32 %value, label %default [i32 1, label %case1]
phi φ(フィ)ノード %result = phi i32 [ %val1, %block1 ], [ %val2, %block2 ]
select 条件付き選択 %result = select i1 %cond, i32 %trueval, i32 %falseval
gep ポインタの算術演算(高度なバージョン) %ptr = getelementptr inbounds i32, i32* %arr, i32 %idx
trunc キャスト(短縮) %result = trunc i64 %val to i32
zext キャスト(ゼロ拡張) %result = zext i32 %val to i64
sext キャスト(符号拡張) %result = sext i32 %val to i64
fptoui 浮動小数点数から符号なし整数へのキャスト %result = fptoui float %val to i32
fptosi 浮動小数点数から符号付き整数へのキャスト %result = fptosi float %val to i32
uitofp 符号なし整数から浮動小数点数へのキャスト %result = uitofp i32 %val to float
sitofp 符号付き整数から浮動小数点数へのキャスト %result = sitofp i32 %val to float
fptrunc 浮動小数点数の短縮 %result = fptrunc double %val to float
fpext 浮動小数点数の拡張 %result = fpext float %val to double
bitcast ビットキャスト %result = bitcast i32* %ptr to i8*
inttoptr 整数からポインタへのキャスト %result = inttoptr i32 %val to i8*
ptrtoint ポインタから整数へのキャスト %result = ptrtoint i8* %ptr to i32
unreachable アンリーチャブル unreachable
これらの命令は、LLVM IRを記述する際に頻繁に使用されます。各命令には、特定の演算や操作を行うためのオペコードが割り当てられています。

これは、LLVM IRの基本的な文法要素の一部です。 LLVM IRは、高度な最適化を行うための中間表現として設計されています。

.c から .ll を生成

編集

hello.cをLLVM IR(.ll形式)にコンパイルするには、次の手順を実行します。

  1. clangコマンドを使用してhello.cをLLVM IRにコンパイルします。
  2. -Sオプションを使用して、LLVM IRをアセンブリ形式(.llファイル)で出力します。

以下は、この手順を示すコマンドです。

clang -S -emit-llvm hello.c -o hello.ll

このコマンドは、hello.cをLLVM IRにコンパイルし、hello.llという名前のLLVM IRファイルを出力します。

hello.c
#include <stdio.h>

auto main(int argc, char* argv[]) -> int {
  printf("Hello World!\n");
}
hello.ll
; ModuleID = 'hello.c'
source_filename = "hello.c"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-freebsd14.0"

@.str = private unnamed_addr constant [14 x i8] c"Hello World!\0A\00", align 1

; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @main(i32 noundef %0, ptr noundef %1) #0 {
  %3 = alloca i32, align 4
  %4 = alloca ptr, align 8
  store i32 %0, ptr %3, align 4
  store ptr %1, ptr %4, align 8
  %5 = call i32 (ptr, ...) @printf(ptr noundef @.str)
  ret i32 0
}

declare dso_local i32 @printf(ptr noundef, ...) #1

attributes #0 = { noinline nounwind optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #1 = { "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }

!llvm.module.flags = !{!0, !1, !2}
!llvm.ident = !{!3}

!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{i32 7, !"uwtable", i32 2}
!2 = !{i32 7, !"frame-pointer", i32 2}
!3 = !{!"FreeBSD clang version 16.0.6 (https://github.com/llvm/llvm-project.git llvmorg-16.0.6-0-g7cbf1a259152)"}

このLLVM IRは、C言語のプログラム("hello.c")から生成されたものです。プログラムは "Hello World!" という文字列を出力する単純なもので、printf関数を使用しています。

まず、IRはモジュールIDやターゲットのデータレイアウト、ターゲットトリプルなどのメタ情報で始まります。次に、文字列 "Hello World!\n" を表す定数である @.str が定義されています。

その後、main関数が定義されています。この関数は、i32型の引数とptr型の引数を取ります。この関数はprintf関数を呼び出して "Hello World!" を出力し、終了コードとして0を返します。

最後に、printf関数が宣言されています。この関数は外部リンケージを持ち、ptr型の引数を受け取り、i32型の値を返します。

それぞれの関数や変数には、アラインメントなどの属性が付与されています。また、LLVMのバージョン情報やモジュールのフラグなどの追加情報も含まれています。 hello.llファイルは、hello.cのコンパイルされたLLVM IRコードを含みます。このファイルをテキストエディタで開くことで、LLVM IRの構文や構造を確認することができます。

LLVMバックエンド

編集

LLVMバックエンドは、IRをターゲットマシンに合わせたコードに変換する役割を担っています。バックエンドは、以下の要素で構成されています。

コード生成器
IRをアセンブリ言語に変換します。
アセンブラ
アセンブリ言語を機械語に変換します。
リンカー
複数のオブジェクトファイルをリンクして、実行可能なプログラムを作成します。

バックエンドは、以下の最適化を実行します。

レジスタ割り当て
命令で使用されるオペランドをレジスタに割り当てます。
命令スケジューリング
命令の順序を最適化します。
メモリアクセス最適化
メモリアクセスを効率化します。

LLVMランタイム

編集

LLVMランタイムは、LLVMコンパイラシステムによって生成されたプログラムの実行をサポートするライブラリです。ランタイムは、以下の機能を提供します。

メモリ管理
プログラムのメモリ割り当てと解放を行います。
例外処理
例外が発生した場合に処理を行います。
並行処理
複数のスレッドでプログラムを実行します。

LLVM ツールチェイン

編集

LLVMツールチェインは、LLVMプロジェクトに含まれる一連のツール群のことです。これらのツールは、LLVMコンパイラを中心として、コンパイル、リンク、最適化、デバッグ、アセンブルなどのタスクを処理するためのものです。主なツールには以下が含まれますが、これに限らず、他のツールも含まれています。

clang
C、C++、Objective-Cなどのプログラミング言語のコンパイラ。GCC互換性を持ちつつも、LLVMのコアコンポーネントを使用して高速で高品質なコンパイルを提供します。
llvm-as / llvm-dis
LLVMのアセンブラおよびディスアセンブラ。アセンブラはLLVMアセンブリ言語をバイナリ形式に変換し、ディスアセンブラはその逆を行います。
llvm-link
LLVMビットコードをリンクするためのツール。複数のビットコードファイルを1つのファイルにリンクします。
opt
LLVMの最適化ツール。LLVM IR上で様々な最適化を適用します。
llvm-dis
LLVMのディスアセンブラ。バイナリ形式のLLVMビットコードをLLVMアセンブリ言語に変換します。
llc
LLVMのコードジェネレータ。LLVM IRをターゲットアーキテクチャのアセンブリ言語に変換します。
LLVMコマンド一覧
コマンド 解説
FileCheck LLVMのテストスクリプトにおいて、ファイルの内容を検証するためのパターンベースのツール
amdgpu-arch AMDGPUのアーキテクチャに関する情報を提供するユーティリティ
analyze-build ソースコード解析ツールを使用して、ビルドエラーを特定し解析するためのスクリプト
bugpoint プログラムの特定の問題を特定し、最小限の再現可能なテストケースを生成するためのツール
c-index-test Clangのインデックス機能のテストスイートを実行するためのツール
clang C、C++、Objective-Cのコンパイラ
clang++ C++のコンパイラ
clang-apply-replacements 変更差分をファイルに適用するためのツール
clang-change-namespace コード内の名前空間を変更するためのツール
clang-check ソースファイルを静的に解析し、潜在的な問題を報告するためのツール
clang-cl Microsoft Visual C++互換のコンパイラ
clang-cpp C/C++のプリプロセッサ
clang-doc ソースコードからドキュメントを生成するためのツール
clang-extdef-mapping 外部定義(external definition)のマッピング情報を提供するツール
clang-format ソースコードのフォーマットを整えるためのツール
clang-include-cleaner ソースコードから不要なインクルード文を削除するためのツール
clang-include-fixer インクルードを自動的に追加または削除するためのツール
clang-linker-wrapper リンク時にClangをラップするためのツール
clang-move ソースファイル内の関数やクラスを別のファイルに移動するためのツール
clang-offload-bundler 複数のクロスコンパイル用のコンパイルオブジェクトをパッケージするためのツール
clang-offload-packager クロスコンパイル用の実行可能ファイルをパッケージするためのツール
clang-pseudo PNaClのシンボルを解析するためのツール
clang-query コードをクエリして結果を表示するためのツール
clang-refactor コードリファクタリングを行うためのツール
clang-rename コード内のシンボルの名前を変更するためのツール
clang-reorder-fields 構造体のフィールドの順序を変更するためのツール
clang-repl Clangのリード-イヴァル-プリントループ(REPL)
clang-scan-deps ソースコードの依存関係をスキャンして表示するためのツール
clang-tblgen TableGenファイルを処理してC++コードを生成するためのツール
clang-tidy コードを静的に解析し、ポテンシャルな問題を報告するためのツール
clangd C++のランゲージサーバー
diagtool Clangの診断ツール
dsymutil デバッグシンボルを処理するためのツール
find-all-symbols ファイル内のシンボルを検索するためのツール
git-clang-format Gitコミットのフォーマットを調整するためのツール
hmaptool ハッシュマップファイルを処理するためのツール
intercept-build ビルドスクリプトをラップして、ビルドの出力をログに記録するためのツール
ld.lld LLVMリンカ
ld64.lld マックOS用のリンカ
lit LLVMのテストフレームワーク
llc LLVM IRをマシンコードに変換するためのツール
lld LLVMプロジェクトの新しいリンカ
lld-link Windows用のリンカ
lldb デバッガ
lldb-argdumper デバッガの引数ダンパー
lldb-instr LLDBの命令インストラクタ
lldb-server LLDBのサーバー
lldb-vscode Visual Studio Code用のデバッガエクステンション
lli LLVM IRのJITコンパイラ
llvm-addr2line アドレスからソースコードの行番号への変換を行うユーティリティ
llvm-ar アーカイブファイルを操作するためのユーティリティ
llvm-as LLVMアセンブリ言語からバイナリ形式のLLVMビットコードへの変換を行うユーティリティ
llvm-bcanalyzer バイトコードファイルの解析ツール
llvm-bitcode-strip ビットコードファイルから不要なセクションを除去するツール
llvm-c-test LLVMのCインターフェイスのテストツール
llvm-cat ファイルの内容を標準出力に出力するユーティリティ
llvm-cfi-verify Control Flow Integrity(CFI)のチェックを行うツール
llvm-config コンパイルされたLLVMの構成情報を提供するユーティリティ
llvm-cov コードカバレッジの情報を提供するツール
llvm-cvtres MSVCのリソースコンバータ
llvm-cxxdump C++デバッグ情報のダンプツール
llvm-cxxfilt C++のシンボルをデマングルするユーティリティ
llvm-cxxmap C++シンボルマップを生成するツール
llvm-debuginfo-analyzer デバッグ情報の解析ツール
llvm-debuginfod デバッグ情報を管理するデーモン
llvm-debuginfod-find デバッグ情報を検索するためのユーティリティ
llvm-diff LLVM IRまたはアセンブリファイル間の差分を生成するツール
llvm-dis LLVMビットコードをLLVMアセンブリ言語に逆アセンブルするツール
llvm-dlltool Windows DLLファイルを操作するツール
llvm-dwarfdump DWARF形式のデバッグ情報をダンプするツール
llvm-dwarfutil DWARF形式のデバッグ情報を処理するユーティリティ
llvm-dwp DWARF形式のデバッグ情報をパッケージするツール
llvm-exegesis マイクロアーキテクチャのプロファイリングと解析を行うツール
llvm-extract ビットコードファイルから特定の関数やグローバル変数を抽出するツール
llvm-gsymutil GSYM(Global System Map)デバッグ情報を処理するツール
llvm-ifs 独自のオブジェクトファイル形式を処理するためのユーティリティ
llvm-install-name-tool macOSのインストール名を変更するツール
llvm-jitlink JITリンクライブラリ
llvm-lib ライブラリアーカイブを操作するためのツール
llvm-libtool-darwin macOS用のライブラリツール
llvm-link LLVMビットコードをリンクするツール
llvm-lipo ファイルからアーキテクチャを抽出または削除するツール
llvm-lit テストスクリプトを実行するためのツール
llvm-lto リンク時最適化のエンジン
llvm-lto2 リンク時最適化のツール
llvm-mc アセンブラとディスアセンブラのツール
llvm-mca マイクロアーキテクチャのパフォーマンス解析ツール
llvm-ml Microsoft Assemblerのエイリアス
llvm-modextract モジュールから情報を抽出するツール
llvm-mt アーカイブファイルを操作するためのツール
llvm-nm オブジェクトファイルのシンボルをリストするツール
llvm-objcopy オブジェクトファイルのコピーと変換を行うツール
llvm-objdump オブジェクトファイルをダンプするツール
llvm-omp-device-info OpenMPデバイスの情報を表示するユーティリティ
llvm-omp-kernel-replay OpenMPターゲットレベルのカーネルのリプレイを行うユーティリティ
llvm-opt-report ビットコードの最適化のレポートを生成するツール
llvm-otool macOSのツール
llvm-pdbutil マイクロソフトのPDBファイルを処理するツール
llvm-profdata プロファイルデータファイルを操作するツール
llvm-profgen プロファイルデータを生成するツール
llvm-ranlib アーカイブファイルに関する情報を生成するツール
llvm-rc リソースコンパイラ
llvm-readelf ELF形式のファイルを読み取るツール
llvm-readobj オブジェクトファイルの情報を表示するツール
llvm-reduce コードを最小限の構造に縮小するためのツール
llvm-remark-size-diff リマークサイズの差分を表示するツール
llvm-remarkutil リマーク情報を操作するツール
llvm-rtdyld ランタイムダイナミックローダー
llvm-sim LLVMのシミュレータ
llvm-size セクションサイズの情報を提供するツール
llvm-split ビットコードファイルを分割するツール
llvm-stress LLVMコンパイラのストレステストツール
llvm-strings バイナリファイルから文字列を抽出するツール
llvm-strip 実行可能ファイルからデバッグ情報を削除するツール
llvm-symbolizer アドレスからシンボル情報を取得するツール
llvm-tapi-diff TAPI(Text-based API)の差分を表示するツール
llvm-tblgen TableGenツール
llvm-tli-checker ターゲットライブラリインターフェースのチェッカー
llvm-undname MicrosoftのC++シンボルをデマングルするツール
llvm-windres Windowsリソースコンパイラ
llvm-xray X-Rayツール
mlir-cpu-runner MLIRプログラムをCPU上で実行するランタイム
mlir-linalg-ods-yaml-gen MLIRのYAML定義を生成するツール
mlir-lsp-server MLIRのLanguage Server Protocol(LSP)サーバー
mlir-opt MLIRプログラムの最適化を行うためのツール
mlir-pdll MLIRのプラグインローダー
mlir-pdll-lsp-server MLIRのプラグインローダーのLSPサーバー
mlir-reduce MLIRプログラムを最小限の形に縮小するためのツール
mlir-tblgen MLIR TableGenツール
mlir-translate MLIRプログラムを他の形式に変換するためのツール
modularize ヘッダーファイルからモジュールファイルを生成するツール
nvptx-arch NVPTXアーキテクチャに関する情報を提供するユーティリティ
opt LLVM IR上で最適化を行うツール
pp-trace プリプロセス中にマクロの置換を追跡するためのツール
run-clang-tidy Clang-Tidyを実行するためのスクリプト
sancov サンディタイザーコードのカバレッジ情報を提供するツール
sanstats サンディタイザーの統計情報を表示するツール
scan-build Clang Static Analyzerを使用して、コードベース全体を解析するためのツール
scan-build-py Pythonスクリプトを使用して、コードベース全体を解析するためのツール
scan-view Clang Static AnalyzerのHTMLレポートを表示するためのツール
tblgen-lsp-server TableGenツールのLSPサーバー
verify-uselistorder リンク時の順序を確認するツール
wasm-ld WebAssemblyリンカ

これらのツールは、LLVMプロジェクトの中核であり、プログラムの開発、デバッグ、最適化、および配布のために広く使用されています。また、LLVMツールチェインは、クロスコンパイルや最適化など、さまざまなターゲットやプラットフォームでの開発にも利用されます。

LLVMのビルドとIR生成

編集

以下のC++コードを作成して、LLVMのIRを生成する方法を学びます。これは、単純な四則演算の関数をLLVM IRに変換するものです。

C++ のソースコードの準備

編集
simple_example.cpp
#include <iostream>
#include <llvm/IR/IRBuilder.h>
#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/Module.h>

int main() {
  llvm::LLVMContext context;
  llvm::Module module("SimpleModule", context);
  llvm::IRBuilder<> builder(context);

  // Create a function prototype
  llvm::FunctionType *funcType =
      llvm::FunctionType::get(builder.getInt32Ty(), false);
  llvm::Function *mainFunc = llvm::Function::Create(
      funcType, llvm::Function::ExternalLinkage, "main", &module);

  // Create a basic block
  llvm::BasicBlock *entry =
      llvm::BasicBlock::Create(context, "entrypoint", mainFunc);
  builder.SetInsertPoint(entry);

  // Perform a simple addition operation
  llvm::Value *valA = llvm::ConstantInt::get(context, llvm::APInt(32, 10));
  llvm::Value *valB = llvm::ConstantInt::get(context, llvm::APInt(32, 20));
  llvm::Value *result = builder.CreateAdd(valA, valB, "addtmp");

  builder.CreateRet(result);

  // Print LLVM IR to console
  module.print(llvm::outs(), nullptr);

  return 0;
}

このC++のコードは、LLVMのC++ APIを使用してLLVM IRを生成する例を示しています。

  1. 最初に、必要なヘッダーファイルをインクルードしています。これらのヘッダーファイルには、LLVMのIRビルダー、コンテキスト、モジュール、および関連する機能が含まれています。
  2. main() 関数の中で、LLVMコンテキストを作成し、SimpleModuleという名前の新しいLLVMモジュールを作成しています。
  3. llvm::IRBuilder<> builder(context); では、IRBuilderオブジェクトが作成されます。このビルダーは、IRを生成するためのメインのツールとして使用されます。
  4. llvm::FunctionType *funcType = llvm::FunctionType::get(builder.getInt32Ty(), false); では、 getInt32Ty() を使用して32ビットの整数型を指定した funcType を作成しています。この関数は整数を返し、引数を取らないと定義されています。
  5. llvm::Function *mainFunc = llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "main", &module); では、この funcType に基づいて main という名前の新しい関数を module に作成しています。これが生成された関数のエントリーポイントになります。
  6. llvm::BasicBlock *entry = llvm::BasicBlock::Create(context, "entrypoint", mainFunc); では、main 関数の中に entrypoint という新しい基本ブロックを作成しています。基本ブロックは、制御フローの開始点です。
  7. builder.SetInsertPoint(entry); は、IRビルダーを entry 基本ブロックに挿入することを指示します。これにより、ここで生成されるIR命令が entry ブロック内に配置されます。
  8. llvm::Value *result = builder.CreateAdd(valA, valB, "addtmp"); では、IRビルダーを使用して valAvalB の加算を表すIR命令が作成され、addtmp という名前が付けられます。その結果を result に格納します。
  9. builder.CreateRet(result); は、result の値を main 関数から返すための ret 命令を作成します。
  10. 最後に、module.print(llvm::outs(), nullptr); を使用して、生成されたLLVM IRを標準出力に出力しています。

このコードは、LLVMのC++ APIを使用してLLVM IRを構築する基本的な方法を示しています。 IRBuilderを使用することで、プログラムでIRを生成するための柔軟性と制御が提供されます。

CMakeLists.txtの準備

編集
CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(simple_example)

# Find LLVM package
find_package(LLVM REQUIRED CONFIG)

# Set include directories for LLVM
include_directories(${LLVM_INCLUDE_DIRS})
add_definitions(${LLVM_DEFINITIONS})

# Add the executable
add_executable(simple_example simple_example.cpp)

# Link LLVM libraries
llvm_map_components_to_libnames(llvm_libs support core irreader)

target_link_libraries(simple_example ${llvm_libs})

このCMakeレシピは、LLVMを使用するC++プロジェクトをビルドするための指示を含んでいます。

  1. cmake_minimum_required(VERSION 3.0): この行は、CMakeの最小バージョンを指定しています。バージョン3.0以上が必要です。
  2. project(simple_example): simple_example というプロジェクト名を指定しています。
  3. find_package(LLVM REQUIRED CONFIG): LLVMを検索し、構成ファイル(LLVMConfig.cmakeなど)を使用してLLVMパッケージを探します。
  4. include_directories(${LLVM_INCLUDE_DIRS}): LLVM_INCLUDE_DIRS に含まれるディレクトリをプロジェクトのインクルードパスに追加します。これにより、LLVMのヘッダーファイルにアクセスできるようになります。
  5. add_definitions(${LLVM_DEFINITIONS}): LLVMの定義(LLVM_DEFINITIONS)をプロジェクトに追加します。これにより、LLVMが定義する任意のマクロや定数がプロジェクトに取り込まれます。
  6. add_executable(simple_example simple_example.cpp): simple_example.cpp をコンパイルして simple_example という名前の実行可能ファイルを作成します。
  7. llvm_map_components_to_libnames(llvm_libs support core irreader): llvm_map_components_to_libnames 関数は、指定されたLLVMのコンポーネント(ここでは support, core, irreader)に対応するライブラリ名を取得します。
  8. target_link_libraries(simple_example ${llvm_libs}): simple_example ターゲットに、LLVMで使用されるライブラリをリンクします。${llvm_libs} には、llvm_map_components_to_libnames で解決されたライブラリ名が含まれます。

このCMakeレシピは、LLVMを検出し、プロジェクトに必要なヘッダーファイルへのアクセスを確立し、LLVMの必要なライブラリをリンクするための手順を含んでいます。これにより、LLVMを使用するC++プロジェクトがビルドされ、実行可能なバイナリが生成されます。

コンパイルと実行

編集

このコードをC++ファイルとして保存し、LLVMを使ってコンパイルします。

% ls
CMakeLists.txt          simple_example.cpp
% mkdir build
% cd build/
% cmake ..
CMake Deprecation Warning at CMakeLists.txt:1 (cmake_minimum_required):
  Compatibility with CMake < 3.5 will be removed from a future version of
  CMake.

  Update the VERSION argument <min> value or use a ...<max> suffix to tell
  CMake that the project does not need compatibility with older versions.


-- The C compiler identification is Clang 16.0.6
-- The CXX compiler identification is Clang 16.0.6
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found ZLIB: /usr/lib/libz.so (found version "1.3")  
-- Found zstd: /usr/local/lib/libzstd.so  
-- Configuring done (0.4s)
-- Generating done (0.0s)
-- Build files have been written to: /home/user1/llvm.simple/build
% make
[ 50%] Building CXX object CMakeFiles/simple_example.dir/simple_example.cpp.o
[100%] Linking CXX executable simple_example
[100%] Built target simple_example
% ./simple_example

コンパイルが成功したら、生成された実行可能ファイルを実行して、LLVM IRを確認します。

% ./simple_example
; ModuleID = 'SimpleModule'
source_filename = "SimpleModule"

define i32 @main() {
entrypoint:
  ret i32 30
}

逆ポーランド電卓

編集

このコードは、LLVMを使用して逆ポーランド記法の電卓を実装しています。基本的に、ユーザーが逆ポーランド記法の式を入力し、その式を評価して結果を表示する簡単なプログラムです。さらに、CMakeを使用してLLVMをリンクし、実行可能ファイルをビルドする手順を提供します。

main.cpp
#include <iostream>
#include <llvm/ADT/STLExtras.h>
#include <llvm/IR/Constants.h>
#include <llvm/IR/IRBuilder.h>
#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/Module.h>
#include <llvm/IR/Type.h>
#include <llvm/IR/Verifier.h>
#include <llvm/Support/raw_ostream.h>
#include <sstream>
#include <stack>
#include <string>
#include <vector>

// 逆ポーランド記法(RPN)式を評価する関数
llvm::Value *calculateRPN(const std::vector<std::string> &tokens, llvm::IRBuilder<> &builder, std::stack<llvm::Value *> &stack) {
  llvm::LLVMContext &context = builder.getContext();

  for (const auto &token : tokens) {
    if (token == "+") {
      llvm::Value *right = stack.top();
      stack.pop();
      llvm::Value *left = stack.top();
      stack.pop();
      stack.push(builder.CreateAdd(left, right, "addtmp")); // 加算を実行し、結果をスタックにプッシュする
    } else if (token == "-") {
      llvm::Value *right = stack.top();
      stack.pop();
      llvm::Value *left = stack.top();
      stack.pop();
      stack.push(builder.CreateSub(left, right, "subtmp")); // 減算を実行し、結果をスタックにプッシュする
    } else if (token == "*") {
      llvm::Value *right = stack.top();
      stack.pop();
      llvm::Value *left = stack.top();
      stack.pop();
      stack.push(builder.CreateMul(left, right, "multmp")); // 乗算を実行し、結果をスタックにプッシュする
    } else if (token == "/") {
      llvm::Value *right = stack.top();
      stack.pop();
      llvm::Value *left = stack.top();
      stack.pop();
      stack.push(builder.CreateSDiv(left, right, "divtmp")); // 除算を実行し、結果をスタックにプッシュする
    } else {
      llvm::Value *num = llvm::ConstantInt::get(
          context, llvm::APInt(32, std::stoi(token), true));
      stack.push(num); // 数値をスタックにプッシュする
    }
  }

  return stack.top(); // スタックの先頭(最終結果)を返す
}

int main() {
  std::string input;
  std::vector<std::string> tokens;
  llvm::LLVMContext context;
  llvm::IRBuilder<> builder(context);
  std::stack<llvm::Value *> stack; // 評価中に値を保持するスタック

  for (;;) {
    std::cout << "逆ポーランド記法の式を入力してください(終了するには 'exit'): ";
    std::getline(std::cin, input);

    if (input == "exit") {
      break;
    }

    std::istringstream iss(input);
    std::string token;

    while (iss >> token) {
      tokens.push_back(token); // 入力されたトークンを保存する
    }

    llvm::Value *result = calculateRPN(tokens, builder, stack); // RPN式を評価する

    llvm::outs() << "結果: ";
    result->print(llvm::outs()); // 最終結果を表示する
    llvm::outs() << "\n";

    tokens.clear(); // 次の入力のためにトークンをクリアする
  }

  return 0;
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(LLVM_RPN_Calculator)

# Find LLVM package
find_package(LLVM REQUIRED CONFIG)

# Set LLVM include directories
include_directories(${LLVM_INCLUDE_DIRS})
add_definitions(${LLVM_DEFINITIONS})

# Set sources
set(SOURCES main.cpp)

# Create executable
add_executable(LLVM_RPN_Calculator ${SOURCES})

# Link LLVM libraries
llvm_map_components_to_libnames(llvm_libs support core irreader)
target_link_libraries(LLVM_RPN_Calculator ${llvm_libs})
  1. cmake_minimum_required(VERSION 3.0): CMakeの最小バージョンを指定しています。
  2. project(LLVM_RPN_Calculator): プロジェクト名を指定しています。
  3. find_package(LLVM REQUIRED CONFIG): LLVMパッケージを検索し、必要な構成を探します。
  4. include_directories(${LLVM_INCLUDE_DIRS})add_definitions(${LLVM_DEFINITIONS}) は、LLVMのインクルードディレクトリを設定し、LLVMが定義するマクロや定数を追加します。
  5. add_executable(LLVM_RPN_Calculator ${SOURCES}): main.cpp を含む実行可能ファイルを作成します。
  6. llvm_map_components_to_libnames(llvm_libs support core irreader)target_link_libraries(LLVM_RPN_Calculator ${llvm_libs}) は、LLVMライブラリをリンクします。

ビルド手順:

編集
  1. プロジェクトディレクトリを作成します。
  2. CMakeLists.txtmain.cpp を作成し、それぞれの内容をファイルに記述します。
  3. ターミナルでプロジェクトディレクトリに移動します。
  4. ビルドディレクトリを作成します。
% mkdir build
% cd build
  1. CMakeを使用してビルドを構成します。
% cmake ..
CMake Deprecation Warning at CMakeLists.txt:1 (cmake_minimum_required):
  Compatibility with CMake < 3.5 will be removed from a future version of
  CMake.

  Update the VERSION argument <min> value or use a ...<max> suffix to tell
  CMake that the project does not need compatibility with older versions.


-- The C compiler identification is Clang 16.0.6
-- The CXX compiler identification is Clang 16.0.6
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found ZLIB: /usr/lib/libz.so (found version "1.3")  
-- Found zstd: /usr/local/lib/libzstd.so  
-- Configuring done (0.4s)
-- Generating done (0.0s)
-- Build files have been written to: /home/user1/llvm.rpn/build
  1. make コマンドでプロジェクトをビルドします。
% make
[ 50%] Building CXX object CMakeFiles/LLVM_RPN_Calculator.dir/main.cpp.o
[100%] Linking CXX executable LLVM_RPN_Calculator
[100%] Built target LLVM_RPN_Calculator

これで、LLVM_RPN_Calculator という名前の実行可能ファイルがビルドされます。実行可能ファイルを実行すると、逆ポーランド記法の式を入力し、計算結果が表示されます。

% ./LLVM_RPN_Calculator
Enter an RPN expression (or 'exit' to quit): 1 2 +
Result: i32 3
Enter an RPN expression (or 'exit' to quit): 12 7 -
Result: i32 5
Enter an RPN expression (or 'exit' to quit): exit

まとめ

編集

LLVMコアは、LLVMコンパイラシステムの基盤であり、以下の重要な役割を担っています。

中間表現(IR)の生成と最適化
言語非依存的なIRを生成し、最適化することで、さまざまなプラットフォーム上で動作するプログラムを生成できます。
ターゲットマシンに合わせたコード生成
ターゲットマシンのアーキテクチャに合わせた効率的なコードを生成できます。
実行時サポート
プログラムの実行に必要なメモリ管理、例外処理、並行処理などの機能を提供します。

LLVMコアは、モジュール方式で設計されており、拡張性、再利用性、移植性に優れています。そのため、LLVMは幅広い用途で利用されており、コンパイラ技術の進歩に大きく貢献しています。

今後の展望

編集

LLVMは、今後も進化し続けていくことが期待されています。今後は、以下の分野での研究開発が進められると考えられます。

より高度な最適化技術
より効率的なコード生成を実現するために、より高度な最適化技術の開発が進められると考えられます。
新しいアーキテクチャへの対応
新たなハードウェアアーキテクチャの登場に合わせて、LLVMコアも対応していく必要があります。
セキュリティの強化
セキュリティ対策の強化も重要な課題です。

LLVMは、コンパイラ技術の未来を担う重要な技術です。今後のLLVMの進化が期待されます。

附録

編集

LLVMコアの用語集

編集
中間表現(IR)
コンパイラフロントエンドから生成される言語依存的なコードを、LLVMバックエンドが理解できる言語非依存的な形式に変換したものです。
静的単一代入形式(SSA)
各変数(型付きレジスタと呼ばれる)は一度だけ代入され、その後は固定されます。
バックエンド
IRをターゲットマシンに合わせたコードに変換する役割を担っています。
ランタイム
LLVMコンパイラシステムによって生成されたプログラムの実行をサポートするライブラリです。
モジュール
特定の機能を提供する独立したコンポーネントです。

LLVMコアの関連技術

編集
コンパイラ
ソースコードを機械語に変換するソフトウェアです。
アセンブリ言語
機械語を人間が読める形式で記述した言語です。
リンカー
複数のオブジェクトファイルをリンクして、実行可能なプログラムを作成するソフトウェアです。
並行処理
複数の処理を同時に実行する処理方法です。

LLVMコアの活用事例

編集
コンパイラ
LLVMは、C、C++、Java、JavaScript、Pythonなどのさまざまな言語のコンパイラに利用されています。
仮想マシン
LLVMは、Java仮想マシン(JVM)や.NET Frameworkなどの仮想マシンのバックエンドに利用されています。
静的解析ツール
LLVMは、静的解析ツールに利用されています。
ドメイン固有言語(DSL)
LLVMは、ドメイン固有言語(DSL)のコンパイラに利用されています。