LLVM/LLVMコア
LLVMコア -- コンパイラシステムの心臓部
[編集]はじめに
[編集]LLVMは、コンパイラシステムのミドルウェアとして、中間表現コード(LLVM IR)を生成し、最適化、ターゲットマシンに合わせたコード生成、実行といった役割を担っています。LLVMコアは、これらの機能を支える基盤であり、LLVMコンパイラシステムの心臓部とも言えます。
本章では、LLVMコアの構成要素と各要素の役割について詳しく説明します。また、各要素の詳細な仕組みや動作についても解説します。
LLVM IR
[編集]LLVM IR(LLVM Intermediate Representation)は、LLVM(Low Level Virtual Machine)コンパイラフレームワークで使用される中間言語の一種です。LLVMは、様々なプログラミング言語(C、C++、Rust、Kotlin Native、Julia、Crystal、Zigなど)のコンパイラや最適化ツールの基盤として使われます。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の基本的な構文や特徴についてのクイックツアーを提供します。
- 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
:関数からの戻り。
- 型付け
- LLVM IRは、強力な型システムを持っています。たとえば、整数、浮動小数点数、ポインタ、ベクトルなどのさまざまな型がサポートされています。また、ポインタ型は明示的なアドレス空間を持ちます。
- 静的単一代入形式(SSA)
- LLVM IRは、静的単一代入形式(SSA)を採用しています。これは、変数が唯一の代入箇所でしか変更されないことを保証する形式です。例えば、以下のコードはSSA形式で表現されます:
%1 = add i32 %a, %b %2 = mul i32 %1, 2
- 最適化
- LLVM IRは、多くの最適化を適用できるよう設計されています。変数の削除やコードの変形など、様々な最適化が可能です。
- データフローグラフ
- LLVM IRは、データフローグラフの形式で表現されます。このため、コードの解析や変換が容易に行えます。
- LLVMツール
- LLVMには、LLVM IRを生成したり操作したりするための様々なツールが含まれています。例えば、
clang
コンパイラは、ソースコードをLLVM IRに変換することができます。また、opt
ツールは、LLVM IR上で様々な最適化を行うことができます。
- LLVMには、LLVM IRを生成したり操作したりするための様々なツールが含まれています。例えば、
これはLLVM IRのクイックツアーの概要です。LLVM IRは、LLVMフレームワークの中核を成す部分であり、その柔軟性と性能は、この中間表現の効果的な設計によるものです。
LLVM IR の文法
[編集]LLVM IRの文法は、アセンブリ言語に似た構文を持ちます。以下に、LLVM IRの基本的な文法要素を示します。
- プログラム構造
- LLVM IRプログラムは、グローバルな定義と関数定義で構成されます。
- グローバルな定義: グローバルな変数や定数などの定義を含みます。
- 関数定義: 関数の定義が含まれます。
- 関数定義
- 関数定義は、次のような構造を持ちます:
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: アラインメント指定(オプション)。
- 命令
- 関数の本体は、命令で構成されます。命令は、次のような形式を持ちます:
<result> = <opcode> [fast-math-flags] [fn attrs] [operand1, operand2, ...]
- result: 命令の結果の格納先。
- opcode: 命令のオペコード(加算、乗算、メモリアクセスなど)。
- fast-math-flags: 高速演算フラグ(オプション)。
- fn attrs: 命令の属性(オプション)。
- operand1, operand2, ...: 命令のオペランド。
- コメント
- コメントは、
;
で始まります。 ; これはコメントです
- コメントは、
以下は、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
形式)にコンパイルするには、次の手順を実行します。
clang
コマンドを使用してhello.c
をLLVM IRにコンパイルします。-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を生成する例を示しています。
- 最初に、必要なヘッダーファイルをインクルードしています。これらのヘッダーファイルには、LLVMのIRビルダー、コンテキスト、モジュール、および関連する機能が含まれています。
main()
関数の中で、LLVMコンテキストを作成し、SimpleModule
という名前の新しいLLVMモジュールを作成しています。llvm::IRBuilder<> builder(context);
では、IRBuilderオブジェクトが作成されます。このビルダーは、IRを生成するためのメインのツールとして使用されます。llvm::FunctionType *funcType = llvm::FunctionType::get(builder.getInt32Ty(), false);
では、getInt32Ty()
を使用して32ビットの整数型を指定したfuncType
を作成しています。この関数は整数を返し、引数を取らないと定義されています。llvm::Function *mainFunc = llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "main", &module);
では、このfuncType
に基づいてmain
という名前の新しい関数をmodule
に作成しています。これが生成された関数のエントリーポイントになります。llvm::BasicBlock *entry = llvm::BasicBlock::Create(context, "entrypoint", mainFunc);
では、main
関数の中にentrypoint
という新しい基本ブロックを作成しています。基本ブロックは、制御フローの開始点です。builder.SetInsertPoint(entry);
は、IRビルダーをentry
基本ブロックに挿入することを指示します。これにより、ここで生成されるIR命令がentry
ブロック内に配置されます。llvm::Value *result = builder.CreateAdd(valA, valB, "addtmp");
では、IRビルダーを使用してvalA
とvalB
の加算を表すIR命令が作成され、addtmp
という名前が付けられます。その結果をresult
に格納します。builder.CreateRet(result);
は、result
の値をmain
関数から返すためのret
命令を作成します。- 最後に、
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++プロジェクトをビルドするための指示を含んでいます。
cmake_minimum_required(VERSION 3.0)
: この行は、CMakeの最小バージョンを指定しています。バージョン3.0以上が必要です。project(simple_example)
:simple_example
というプロジェクト名を指定しています。find_package(LLVM REQUIRED CONFIG)
: LLVMを検索し、構成ファイル(LLVMConfig.cmake
など)を使用してLLVMパッケージを探します。include_directories(${LLVM_INCLUDE_DIRS})
:LLVM_INCLUDE_DIRS
に含まれるディレクトリをプロジェクトのインクルードパスに追加します。これにより、LLVMのヘッダーファイルにアクセスできるようになります。add_definitions(${LLVM_DEFINITIONS})
: LLVMの定義(LLVM_DEFINITIONS
)をプロジェクトに追加します。これにより、LLVMが定義する任意のマクロや定数がプロジェクトに取り込まれます。add_executable(simple_example simple_example.cpp)
:simple_example.cpp
をコンパイルしてsimple_example
という名前の実行可能ファイルを作成します。llvm_map_components_to_libnames(llvm_libs support core irreader)
:llvm_map_components_to_libnames
関数は、指定されたLLVMのコンポーネント(ここではsupport
,core
,irreader
)に対応するライブラリ名を取得します。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})
cmake_minimum_required(VERSION 3.0)
: CMakeの最小バージョンを指定しています。project(LLVM_RPN_Calculator)
: プロジェクト名を指定しています。find_package(LLVM REQUIRED CONFIG)
: LLVMパッケージを検索し、必要な構成を探します。include_directories(${LLVM_INCLUDE_DIRS})
とadd_definitions(${LLVM_DEFINITIONS})
は、LLVMのインクルードディレクトリを設定し、LLVMが定義するマクロや定数を追加します。add_executable(LLVM_RPN_Calculator ${SOURCES})
:main.cpp
を含む実行可能ファイルを作成します。llvm_map_components_to_libnames(llvm_libs support core irreader)
とtarget_link_libraries(LLVM_RPN_Calculator ${llvm_libs})
は、LLVMライブラリをリンクします。
ビルド手順:
[編集]- プロジェクトディレクトリを作成します。
CMakeLists.txt
とmain.cpp
を作成し、それぞれの内容をファイルに記述します。- ターミナルでプロジェクトディレクトリに移動します。
- ビルドディレクトリを作成します。
% mkdir build % cd build
- 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
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)のコンパイラに利用されています。