LLVMは非常にパワフルなツールセットであり、その使い方を学ぶためのチュートリアルが公式に提供されています。
以下は、LLVMの基本的な使い方を学ぶための簡単なチュートリアルの手順です。
LLVMをインストールします。公式のウェブサイト（ https://llvm.org/ ）から、最新バージョンをダウンロードしてインストールするか、パッケージマネージャーを使用してインストールします。
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プロジェクトの中核であり、プログラムの開発、デバッグ、最適化、および配布のために広く使用されています。また、LLVMツールチェインは、クロスコンパイルや最適化など、さまざまなターゲットやプラットフォームでの開発にも利用されます。
LLVM IR（LLVM Intermediate Representation）は、LLVM（Low Level Virtual Machine）コンパイラフレームワークで使用される中間言語の一種です。LLVMは、様々なプログラミング言語（C、C++、Rustなど）のコンパイラや最適化ツールの基盤として使われます。LLVM IRは、これらの言語のソースコードをコンパイルした後の中間形式の表現です。
LLVM IRは、低レベルなアセンブリ言語に近い形式でありながら、高レベルな抽象化を持っています。LLVM IRは、構造化されたプログラム表現を提供し、機械による解析や最適化が行いやすいように設計されています。
LLVM IRの特徴は次のとおりです：
プラットフォーム中立性: LLVM IRは、プラットフォームに依存しない中間表現であり、様々なアーキテクチャやOSに対応しています。
型付け: LLVM IRは型付き言語です。つまり、変数や関数に型が付与され、型の整合性が検証されます。
SSA形式: 静的単一代入形式（Static Single Assignment、SSA）形式で表現されます。これは、変数が唯一の代入箇所でしか変更されないことを保証する形式で、多くの最適化手法に適しています。
中間表現の最適化: LLVM IRは、高度な最適化を可能にする設計がなされています。このため、コンパイラが生成する様々な最適化を適用することができます。
直観的な構造: LLVM IRは人間にとっても比較的読みやすい構造を持っています。これは、デバッグや解析を容易にするために重要です。
LLVM IRは、ソースコードから生成され、LLVMコンパイラによって様々な最適化が適用された後、最終的にターゲットのマシンコードに変換されます。LLVMの柔軟性と性能の高さは、その中間表現である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は、強力な型システムを持っています。たとえば、整数、浮動小数点数、ポインタ、ベクトルなどのさまざまな型がサポートされています。また、ポインタ型は明示的なアドレス空間を持ちます。
%1 = add i32 %a, %b
%2 = mul i32 %1, 2
LLVM IRは、多くの最適化を適用できるよう設計されています。変数の削除やコードの変形など、様々な最適化が可能です。
LLVM IRは、データフローグラフの形式で表現されます。このため、コードの解析や変換が容易に行えます。
LLVMには、LLVM IRを生成したり操作したりするための様々なツールが含まれています。例えば、clangコンパイラは、ソースコードをLLVM IRに変換することができます。また、optツールは、LLVM IR上で様々な最適化を行うことができます。
これはLLVM IRのクイックツアーの概要です。LLVM IRは、LLVMフレームワークの中核を成す部分であり、その柔軟性と性能は、この中間表現の効果的な設計によるものです。
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]  ( , ...) [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: アラインメント指定（オプション）。
 =  [fast-math-flags] [fn attrs] [operand1, operand2, ...]
result: 命令の結果の格納先。
opcode: 命令のオペコード（加算、乗算、メモリアクセスなど）。
fast-math-flags: 高速演算フラグ（オプション）。
fn attrs: 命令の属性（オプション）。
operand1, operand2, ...: 命令のオペランド。
コメントは、;で始まります。
これはコメントです
以下は、LLVM IRでよく使用される命令の一覧を表組みで示したものです。
これらの命令は、LLVM IRを記述する際に頻繁に使用されます。各命令には、特定の演算や操作を行うためのオペコードが割り当てられています。
これは、LLVM IRの基本的な文法要素の一部です。
LLVM IRは、高度な最適化を行うための中間表現として設計されています。
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:
int main(int argc, char* argv[]) {
  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の構文や構造を確認することができます。
以下のC++コードを作成して、LLVMのIRを生成する方法を学びます。これは、単純な四則演算の関数をLLVM IRに変換するものです。
simple_example.cpp:
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:
cmake_minimum_required(VERSION 3.0)
project(simple_example)
find_package(LLVM REQUIRED CONFIG)
include_directories(${LLVM_INCLUDE_DIRS})
add_definitions(${LLVM_DEFINITIONS})
add_executable(simple_example simple_example.cpp)
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  value or use a ... 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:
// 逆ポーランド記法（RPN）式を評価する関数
llvm::Value *calculateRPN(const std::vector &tokens, llvm::IRBuilder<> &builder, std::stack &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 tokens;
  llvm::LLVMContext context;
  llvm::IRBuilder<> builder(context);
  std::stack 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_package(LLVM REQUIRED CONFIG)
include_directories(${LLVM_INCLUDE_DIRS})
add_definitions(${LLVM_DEFINITIONS})
set(SOURCES main.cpp)
add_executable(LLVM_RPN_Calculator ${SOURCES})
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  value or use a ... 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は非常に広範な機能を持っていますので、より高度な機能や最適化、コード生成などについては、公式ドキュメントや他のチュートリアルを参照することをお勧めします。