単子葉類プログラマーのメモ

プログラミング関連の自分用メモだけど他の人の役に立つかもしれないので公開しておく感じのブログ

LLVM C++ API 学習メモ(1) - LLVM IRの生成と文字列形式での出力

戻り値を返すだけのLLVM IRの作成、LLVM IR文字列の出力方法についてのメモ。

この手順LLVM 10.0.0をビルドした環境で、Visual Studioのプロジェクトをこの内容に設定してビルド確認している。

目次

目的のLLVM IRコード

この記事で作成した以下のようなLLVM IRを出力するプログラムを、LLVM APIC++)で作成する。

define i32 @main() {
    ret i32 111
}

C++ソースコード

#include "llvm/IR/LLVMContext.h"  // for `llvm::LLVMContext`
#include "llvm/IR/Module.h"       // for `llvm::Module`
#include "llvm/IR/Function.h"     // for `llvm::Function` and `llvm::FunctionType`
#include "llvm/IR/BasicBlock.h"   // for `llvm::BasicBlock`
#include "llvm/IR/IRBuilder.h"    // for `llvm::IRBuilder`
#include "llvm/IR/Verifier.h"     // for `llvm::outs()`

int main() {
    llvm::LLVMContext context;

    // Moduleを作成
    llvm::Module mainModule("", context);

    // 関数の型を作成
    llvm::FunctionType* functionType = llvm::FunctionType::get(
        llvm::Type::getInt32Ty(context),    // 戻り値の型
        false                               // 可変長引数の有無
    );

    // main関数`define i32 @main()`を作成 
    llvm::Function* fMain = llvm::Function::Create(
        functionType,                        // 関数の型
        llvm::Function::ExternalLinkage,     // リンケージ
        "main",                              // 関数名
        mainModule                           // この関数の追加先モジュール
    );

    // main関数内にBasicBlockを作成
    llvm::BasicBlock* basicBlock = llvm::BasicBlock::Create(context, "", fMain);

    // BasicBlockに命令を追加
    llvm::IRBuilder<> builder(context);
    builder.SetInsertPoint(basicBlock); // basicBlockの末尾に命令を追加するように設定
    builder.CreateRet(builder.getInt32(111)); // `return i32 111`を追加

    // 標準出力に作成したLLVM IRを出力
    mainModule.print(llvm::outs(), nullptr);

    return 0;
}

使用したクラスのdoxygenへのリンクと概要。

クラス doxygen
LLVMContext LLVMのグローバルデータを管理するクラス
Module モジュール
Function 関数
FunctionType 関数の型を表すクラス
BasicBlock BasicBlock。1つ以上の命令のかたまり。ラベルで始まりbr命令やret命令等で終わるブロック。
IRBuilder 命令を表すLLVM IRを生成し、それをBasicBlockに挿入するためのクラス。

解説

Moduleの作成

llvm::LLVMContext context;
llvm::Module mainModule("", context);

LLVMContextクラスはLLVM全般にかかわる情報を内部に持つ。Moduleの作成以外にもいろいろな操作の際に使用する。

LLVM IRは、Moduleという入れ物の中にグローバル変数や関数を追加する形で作成する。 大雑把に言うと、Moduleは一つの.llファイルに相当する。

Moduleのコンストラクタの第一引数は、Moduleの名前。

Moduleのコンストラクタの第二引数は、LLVMContextオブジェクト。 特に何も考えずにLLVMContextオブジェクトを指定しておけばよさそう。 ただし、LLVMContextオブジェクトを複数のスレッドで使用する場合は、排他制御を自分で行う必要がある。

今回はModuleの名前に空文字列を指定しているが、何らかの名前をつけると以下のようなLLVM IRが追加で出力される。

; ModuleID = 'moduleName'
source_filename = "moduleName"

source_filenameは、プロファイルデータで使用するローカル関数のユニークでグローバルな識別子を生成するために使われる。 その際、ローカル関数名の先頭にソースファイル名が付加される。

ここでいうプロファイルデータというのは、最適化に使用するものと思われる(参考)。

関数の型の作成

llvm::FunctionType* functionType = llvm::FunctionType::get(
    llvm::Type::getInt32Ty(context),
    false
);

関数を作成する前に、関数の型FunctionTypeを作成しておく。

関数の型は、FunctionType::get関数で取得する。

get関数の第一引数に、関数の戻り値の型を指定する。

第二引数は、Functionの引数に可変長引数があるか否かを指定する。

関数の作成

llvm::Function* fMain = llvm::Function::Create(
    functionType,
    llvm::Function::ExternalLinkage,
    "main",
    mainModule
);

関数はFunctionクラスで作成する。

Create関数の第一引数で、関数の型を指定する。

Create関数の第二引数には、Functionのリンケージを指定する。 よくわからないうちは、とにかくllvm::Function::ExternalLinkageを指定しておけばよさそう。

第三引数には関数名を指定する。

第四引数には、Functionの追加先のModuleを指定する。

BasicBlockの作成

llvm::BasicBlock* basicBlock = llvm::BasicBlock::Create(context, "", fMain);

BasicBlock::Create関数で、BasicBlockを作成してFunctionに追加する。

第一引数は、LLVMContextオブジェクト。

第二引数は、BasicBlockにつけるラベル名。 正確には、Twineクラスのオブジェクトを指定するのだが、今のところ必要なさそうなのでTwineの理解は後回しにしておく。

第三引数は、BasicBlockの追加先のFunction。

命令の作成

llvm::IRBuilder<> builder(context);
builder.SetInsertPoint(basicBlock);
builder.CreateRet(builder.getInt32(111));

BasicBlockを作成したら、その中に命令を追加していく。

そのためにIRBuilderクラスを利用する。 今回はテンプレート引数はすべてデフォルト値でいいので、llvm::IRBuilder<> builder(context);のようにオブジェクトを作成する。

builder.SetInsertPoint(basicBlock);で、命令を挿入する位置を、basicBlockの末尾に指定する。

builder.CreateRet(builder.getInt32(111));で、ret i32 111命令を作成し、指定した位置に挿入する。

IRBuilderにはCreate〇〇〇という形式のメンバ関数が多数あり、それらはLLVM IRの各命令に対応している。 命令の一覧と詳細については、IRBuilderのdoxygenを見るよりInstruction-referenceを見たほうがたぶん良い。

LLVM IRの出力

mainModule.print(llvm::outs(), nullptr);

上記で、文字列形式のLLVM IRを標準出力に出力できる。

もし標準エラー出力に出力したければllvm::outs()llvm::errs()にすればよい。

第二引数にはAssemblyAnnotationWriterを指定する。 とりあえずexampleをまねてnullptrを指定しておく。理解は後回し。

参考

Module、Function、BasicBlock、Instructionの関係は以下の説明がわかりやすい。

qiita.com

愚痴

公式のdoxygenを読んでも使い方が全然わからなくて辛い。