LLVM フレームワークで実用的なコンパイラーを作成する: 第 1 回

LLVM とその中間表現を使用してカスタム・コンパイラーを作成する

LLVM コンパイラー・インフラストラクチャーは、どのプログラミング言語を使用するかに関わらず、アプリケーションを最適化する強力な手段です。この全 2 回からなる連載の第 1 回では、LLVM の基礎を学びます。LLVM によってカスタム・コンパイラーを簡単に作成する方法を学んでください!

2012年 6月 19日 ― この連載の第 2 回へのリンクを記事の導入部分、「まとめ」、「参考文献」に追加しました。

Arpan Sen, Author, Independent

Arpan Sen は電子設計自動化業界でソフトウェア開発に従事する先進的なエンジニアです。彼はこれまで、Solaris や SunOS、HP-UX、IRIX など、さまざまな種類の UNIX を扱ってきており、また Linux と Microsoft Windows にも数年の経験があります。彼はソフトウェアのパフォーマンス最適化技術やグラフ理論、並列コンピューティングに大きな関心を持っています。彼はソフトウェア・システムの修士号を取得しています。


developerWorks 貢献著者レベル

2012年 7月 05日

LLVM (Low Level Virtual Machine) は、任意のプログラミング言語で作成されたプログラムをコンパイル時、リンク時、実行時に最適化するために設計された、極めて強力なコンパイラー・インフラストラクチャー・フレームワークです。LLVM は多岐に渡るプラットフォームで動作し、その最大の特徴である、高速に実行されるコードを生成します。

この連載の他の記事

連載「LLVM フレームワークで実用的なコンパイラーを作成する」の他の記事を見る

LLVM フレームワークは、十分なドキュメントが用意された、コードの中間表現 (IR: Intermediate Representation) を中心に構築されています。全 2 回からなる連載の第 1 回目となるこの記事では、LLVM IR の基礎と、その特異な癖のいくつかを詳しく説明した後、LLVM IR の生成作業を自動化するコード・ジェネレーターを作成します。LLVM IR ジェネレーターがあれば、後はお気に入りの言語のフロントエンドをプラグインするだけで、処理フロー全体 (フロントエンドのパーサー + IR ジェネレーター + LLVM バックエンド) が完成します。LLVM によってカスタム・コンパイラーを簡単に作成できるのです!

LLVM の開始準備

記事の本題に入るには、お使いの開発用コンピューターで LLVM がコンパイルされていなければなりません (「参考文献」のリンクを参照)。この記事で使用するサンプル・コードは、LLVM バージョン 3.0 をベースとしています。LLVM コードのポスト・ビルドおよびインストールでは、llclli の 2 つが最も重要なツールとなります。

llc と lli

LLVM は仮想マシン (VM) であることから、独自の中間バイト・コード表現があると考えるのが当然でしょう。最終的には、LLVM のバイト・コードをプラットフォーム固有のアセンブリー言語にコンパイルしなければなりません。そうすることで、プラットフォームにネイティブのアセンブラーとリンカーを使用してアセンブリー・コードを実行し、実行可能ファイルや共有ライブラリーなどを生成することが可能になります。llc は、LLVM のバイト・コードをプラットフォーム固有のアセンブリー・コードに変換するために使用するツールです (このツールの詳細については、「参考文献」のリンクを参照)。LLVM バイト・コードを部分的に直接実行する場合、プログラムにバグが潜んでいるかどうかを知るために、ネイティブ実行可能ファイルが異常な動作を示すようになるまで待つ必要はありません。そのような場合には、バイト・コードを直接実行できる lli が役に立ちます。lli はこの巧みな動作を、インタープリターまたは内部で JIT (Just-In-Time) コンパイラーを使用して実現します。lli についての詳細は、「参考文献」のリンクを参照してください。

llvm-gcc

llvm-gcc は、-S -emit-llvm オプションを指定して実行することで LLVM バイト・コードを生成できるように、GNU コンパイラー・コレクション (gcc) に変更を加えたものです。生成されたバイト・コード (LLVM アセンブリーとも呼ばれます) は、lli を使用して実行することができます。llvm-gcc についての詳細は、「参考文献」を参照してください。お使いのシステムに llvm-gcc がプリインストールされていない場合には、ソースからビルドすることができます。「参考文献」に、その手順をステップ・バイ・ステップで説明しているガイドへのリンクが記載してあるので参照してください。


LLVM による Hello World プログラム

LLVM をより深く理解するためには、LLVM IR とその特異性を学ぶ必要があります。これは、新しいプログラミング言語を学ぶ過程と同様ですが、CC++、そしてそれぞれの特異な癖を経験済みであれば、LLVM IR の特異性にそれほど怖気づくことはありません。リスト 1 に、コンソール出力に「Hello World」と表示する、LLVM 入門者向けのプログラムを記載します。このコードをコンパイルするには、llvm-gcc を使用します。

リスト 1. お馴染みの Hello World プログラム
#include <stdio.h>
int main( )
{ 
  printf("Hello World!\n");
}

このコードをコンパイルするためには、以下のコマンドを入力します。

Tintin.local# llvm-gcc helloworld.cpp -S -emit-llvm

コンパイルの完了後、llvm-gcc は helloworld.s ファイルを生成します。lli を使用してこのファイルを実行すると、メッセージがコンソールに出力されます。lli の使用方法は以下のとおりです。

Tintin.local# lli helloworld.s
Hello, World

リスト 2 に示す初めての LLVM アセンブリー・コードを見てください。

リスト 2. Hello World プログラムの LLVM バイト・コード
@.str = private constant [13 x i8] c"Hello World!\00", align 1 ;

define i32 @main() ssp {
entry:
  %retval = alloca i32
  %0 = alloca i32
  %"alloca point" = bitcast i32 0 to i32
  %1 = call i32 @puts(i8* getelementptr inbounds ([13 x i8]* @.str, i64 0, i64 0))
  store i32 0, i32* %0, align 4
  %2 = load i32* %0, align 4
  store i32 %2, i32* %retval, align 4
  br label %return
return:
  %retval1 = load i32* %retval
  ret i32 %retval1
}

declare i32 @puts(i8*)

LLVM IR の基礎知識

LLVM には、詳細なアセンブリー言語表現が伴います (「参考文献」のリンクを参照)。前述の Hello World を基にしたオリジナルのプログラムの作成に取り掛かる前に、知っておかなければならない事項を以下に記載します。

  • LLVM アセンブリー内のコメントは、セミコロン (;) で始まり、行の終わりまで続きます。
  • グローバル ID は、アット・マーク (@) で始まります。すべての関数名およびグローバル変数も @ で始める必要があります。
  • LLVM 内のローカル ID は、パーセント記号 (%) で始まります。ID に使用できる通常の正規表現は、[%@][a-zA-Z$._][a-zA-Z$._0-9]* です。
  • LLVM は強い型付けのシステムです。それは、LLVM の最も重要な機能にも言えることです。LLVM は、整数型を iN として定義します。N は、その整数のビット幅であり、1 から 223 - 1 までの任意のビット幅を指定することができます。
  • ベクトル型または配列型は、[<要素数> x <各要素のサイズ>] として宣言します。文字列 “Hello World!” の場合、各文字は 1 バイトであるとし、NULL 文字用の 1 バイトを追加して計上すると、その型は [13 x i8] になります。
  • hello-world 文字列のグローバル文字列定数を宣言するには、@hello = constant [13 x i8] c"Hello World!\00" とします。このように constant キーワードを使用して定数を宣言し、その後に型と値を続けます。型についてはすでに説明したので、ここでは値について説明します。値を指定するには、c の後に二重引用符で囲んだ文字列全体を続けます。二重引用符の中には \0 を含めて、さらに 0 で終了してください。文字列を宣言する際には接頭辞 c を使用し、文字列の最後に NULL 文字と 0 の両方を含めなければならない理由については、残念ながら LLVM のドキュメントには説明されていません。LLVM の特異な癖を詳しく調べるには、「参考文献」のリンクから文法ファイルを参照してください。
  • LLVM では、ユーザーが関数を宣言して定義することができます。ここでは LLVM の関数に備わる全機能を網羅する代わりに、必要最小限の要点に絞って説明します。関数を定義するには、define キーワードの後に戻り値の型、そして関数名を続けます。32 ビットの整数を返す単純な main の定義は、define i32 @main() { ; (i32 を返す何らかの LLVM アセンブリー・コード) } となります。
  • 関数の宣言には、関数の定義と同じく多くの内容が含まれます。例えば、LLVM で printf に相当する puts メソッドの最も単純な宣言は、declare i32 puts(i8*) です。宣言は declare キーワードで始まり、その後に戻り値の型、関数名、そしてオプションで関数の引数のリストが続きます。宣言は、グローバル・スコープで行う必要があります。
  • それぞれの関数は、復帰命令で終わります。復帰命令には、ret <型> <値>ret void の 2 つの形があります。この単純な main ルーチンには、ret i32 0 で十分です。
  • 関数を呼び出すには、call <関数の戻り値の型> <関数名> <関数の引数 (オプション)> とします。関数のそれぞれの引数の前に、その型を指定することに注意してください。6 ビットの整数を返し、36 ビットの整数を引数に取る test 関数の場合、その構文は call i6 @test( i36 %arg1 ) となります。

手始めとしては、以上の知識があれば十分です。自作の Hello World プログラムを作成するには、main ルーチン、文字列を格納する定数、そして実際の出力を処理する puts メソッドの宣言を定義する必要があります。リスト 3 に、最初に作成してみたプログラムを示します。

リスト 3. 自作の Hello World プログラムを作成する最初の試み
declare  i32 @puts(i8*) 
@global_str = constant [13 x i8] c"Hello World!\00"
define i32 @main { 
  call i32 @puts( [13 x i8] @global_str )
  ret i32 0 
}

以下は、lli からのログです。

lli: test.s:5:29: error: global variable reference must have pointer type
  call i32 @puts( [13 x i8] @global_str )
                            ^

あいにく、思ったような結果にはなりませんでした。何が起こったのでしょう?前述したように、LLVM が強力な型システムであることを思い出してください。つまり、puts は引数として i8 を指すポインターを想定していましたが、i8 型のベクトルが渡されたため、lli が即座にエラーを出力したということです。C プログラミングの経験者であれば、この問題を修正する当然の方法として、型キャストを使用します。そこで話題に上ってくるのが、getelementptr という LLVM の命令です。リスト 3puts 呼び出しは、例えば call i32 @puts(i8* %t) のように変更しなければならないことに注意してください (ここで、%ti8* 型です。これは、[13 x i8] から i8* に型をキャストした結果です。getelementptr の詳細については、「参考文献」のリンクを参照してください)。先に進む前に、思った通りに動作するコードをリスト 4 に記載します。

リスト 4. getelementptr を使用してポインターへの型キャストを正しく行う
declare i32 @puts (i8*)
@global_str = constant [13 x i8] c"Hello World!\00"

define i32 @main() {
  %temp = getelementptr [13 x i8]*  @global_str, i64 0, i64 0
  call i32 @puts(i8* %temp)
  ret i32 0
}

getelementptr には、最初の引数としてグローバル文字列変数へのポインターを渡します。最初のインデックス i64 0 は、ポインターをグローバル変数までステップ・オーバーするために必要なものです。getelementptr 命令に渡す最初の引数は常に pointer 型の値でなければならないため、最初のインデックスによってそのポインターまでステップ・スルーされます。0 という値は、そのポインターからオフセットされる要素がないことを意味します。私の開発用コンピューターは 64 ビット版 Linux を実行しているため、このポインターは 8 バイトになります。2 番目のインデックス i64 0 は、文字列の 0 個目の要素を選択するために使用されるもので、この要素が puts に引数として渡されます。


カスタム LLVM IR コード・ジェネレーターの作成

LLVM IR についての基礎知識は以上で十分ですが、皆さんに必要なのは、LLVM アセンブリーをダンプする自動コード生成システムです。有難いことに、LLVM には、この自動コード生成システムの作成をサポートするアプリケーション・プログラミング・インターフェース (API) が十分に揃っています (「参考文献」でプログラマー向けマニュアルへのリンクを参照してください)。まずは、開発用コンピューターで LLVMContext.h ファイルを見つけてください。このファイルがない場合、LLVM のインストール方法に、何らかの誤りがあった可能性があります。

ここからは、前述した Hello World プログラムの LLVM IR を生成するプログラムの作成に取り掛かります。ここで作成するプログラムは、LLVM API のすべてを扱うわけではありませんが、以降に記載するサンプル・コードから、LLVM API の大半が直観的で簡単に使えることが明らかになるはずです。

LLVM には、llvm-config という優れたツールが付属しています (「参考文献」を参照)。llvm-config --cxxflags を実行すると、g++ に渡す必要のあるコンパイル・フラグの情報が得られ、llvm-config --ldflags を実行するとリンカー・オプションが得られ、llvm-config --libs を実行すると LLVM ライブラリーに対してリンクする必要があるオブジェクト・ライブラリーの情報が得られます。リスト 5 に記載するサンプル・コードには、g++ に渡さなければならないすべてのオプションが示されています。

リスト 5. LLVM API を使用してコードをビルドするために llvm-config を利用する
tintin# llvm-config --cxxflags --ldflags --libs \
-I/usr/include  -DNDEBUG -D_GNU_SOURCE \
-D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS \
-D__STDC_LIMIT_MACROS -O3  -fno-exceptions -fno-rtti -fno-common \
-Woverloaded-virtual -Wcast-qual \
-L/usr/lib  -lpthread -lm \
-lLLVMXCoreCodeGen -lLLVMTableGen -lLLVMSystemZCodeGen \
-lLLVMSparcCodeGen -lLLVMPTXCodeGen \
-lLLVMPowerPCCodeGen -lLLVMMSP430CodeGen -lLLVMMipsCodeGen \
-lLLVMMCJIT -lLLVMRuntimeDyld \
-lLLVMObject -lLLVMMCDisassembler -lLLVMXCoreDesc -lLLVMXCoreInfo \
-lLLVMSystemZDesc -lLLVMSystemZInfo \
-lLLVMSparcDesc -lLLVMSparcInfo -lLLVMPowerPCDesc -lLLVMPowerPCInfo \
-lLLVMPowerPCAsmPrinter \
-lLLVMPTXDesc -lLLVMPTXInfo -lLLVMPTXAsmPrinter -lLLVMMipsDesc \
-lLLVMMipsInfo -lLLVMMipsAsmPrinter \
-lLLVMMSP430Desc -lLLVMMSP430Info -lLLVMMSP430AsmPrinter \
-lLLVMMBlazeDisassembler -lLLVMMBlazeAsmParser \
-lLLVMMBlazeCodeGen -lLLVMMBlazeDesc -lLLVMMBlazeAsmPrinter \
-lLLVMMBlazeInfo -lLLVMLinker -lLLVMipo \
-lLLVMInterpreter -lLLVMInstrumentation -lLLVMJIT -lLLVMExecutionEngine \
-lLLVMDebugInfo -lLLVMCppBackend \
-lLLVMCppBackendInfo -lLLVMCellSPUCodeGen -lLLVMCellSPUDesc \
-lLLVMCellSPUInfo -lLLVMCBackend \
-lLLVMCBackendInfo -lLLVMBlackfinCodeGen -lLLVMBlackfinDesc \
-lLLVMBlackfinInfo -lLLVMBitWriter \
-lLLVMX86Disassembler -lLLVMX86AsmParser -lLLVMX86CodeGen \
-lLLVMX86Desc -lLLVMX86AsmPrinter -lLLVMX86Utils \
-lLLVMX86Info -lLLVMAsmParser -lLLVMARMDisassembler -lLLVMARMAsmParser \
-lLLVMARMCodeGen -lLLVMARMDesc \
-lLLVMARMAsmPrinter -lLLVMARMInfo -lLLVMArchive -lLLVMBitReader \
-lLLVMAlphaCodeGen -lLLVMSelectionDAG \
-lLLVMAsmPrinter -lLLVMMCParser -lLLVMCodeGen -lLLVMScalarOpts \
-lLLVMInstCombine -lLLVMTransformUtils \
-lLLVMipa -lLLVMAnalysis -lLLVMTarget -lLLVMCore -lLLVMAlphaDesc \
-lLLVMAlphaInfo -lLLVMMC -lLLVMSupport

LLVM モジュール、コンテキスト、その他

LLVM モジュール・クラスは、他のすべての LLVM IR オブジェクトの最上位レベルのコンテナーです。LLVM モジュール・クラスには、グローバル変数、関数、そのモジュールが依存する他のモジュール、シンボル・テーブルなどのリストを含めることができます。LLVM モジュールのコンストラクターは以下のとおりです。

explicit Module(StringRef ModuleID, LLVMContext& C);

作成するプログラムは、まず LLVM モジュールを作成するところから始める必要があります。最初の引数は、モジュールの名前です。これは任意のダミー文字列で構いません。2 番目の引数は、LLVMContext と呼ばれるものです。LLVMContext は若干わかりにくいクラスですが、変数などを作成するコンテキストを提供するクラスであることを理解していれば十分です。このクラスは、スレッドごとにローカル・コンテキストを作成する必要があるような、複数のスレッドが関与するコンテキストで重要になってきます。この場合、各スレッドは他のあらゆるコンテキストとは完全に独立して実行されます。ここではとりあえず、LLVM が提供するデフォルトのグローバル・コンテキスト・ハンドルを使用します。モジュールを作成するコードを以下に記載します。

llvm::LLVMContext& context = llvm::getGlobalContext();

llvm::Module* module = new llvm::Module("top", context);

知っておくべき次の重要なクラスは、IRBuilder です。このクラスは、LLVM 命令を作成して、これらの命令を基本ブロックに挿入するための API を実際に提供します。IRBuilder には多数のオプションがありますが、私はこのクラスを作成するのにおそらく最も簡単な方法として、グローバル・コンテキストを渡すことにしました。それには、以下のコードを使用します。

llvm::LLVMContext& context = llvm::getGlobalContext();

llvm::Module* module = new llvm::Module("top", context);

llvm::IRBuilder<> builder(context);

LLVM オブジェクト・モデルが用意できたら、モジュールの dump メソッドを呼び出すことで、その内容をダンプすることができます。リスト 6 にコードを記載します。

リスト 6. ダミー・モジュールを作成する
#include "llvm/LLVMContext.h"
#include "llvm/Module.h"
#include "llvm/Support/IRBuilder.h"

int main()
{
  llvm::LLVMContext& context = llvm::getGlobalContext();
  llvm::Module* module = new llvm::Module("top", context);
  llvm::IRBuilder<> builder(context); 

  module->dump( );
}

リスト 6 のコードを実行すると、以下の内容がコンソールに出力されます。

; ModuleID = 'top'

次に作成しなければならないのは、main メソッドです。LLVM には、関数を作成するための llvm::Function、そして関数の戻り値の型を関連付けるための llvm::FunctionType があります。また、main メソッドはモジュールの一部として含めなければなければならないことに注意してください。リスト 7 にコードを記載します。

リスト 7. main メソッドを最上位のモジュールに追加する
#include "llvm/LLVMContext.h"
#include "llvm/Module.h"
#include "llvm/Support/IRBuilder.h"

int main()
{
  llvm::LLVMContext& context = llvm::getGlobalContext();
  llvm::Module *module = new llvm::Module("top", context);
  llvm::IRBuilder<> builder(context); 

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

  module->dump( );
}

mainvoid を返すようにするために、builder.getVoidTy() を呼び出していることに注意してください 。maini32 を返すようにするのであれば、builder.getInt32Ty() を呼び出すことになるはずです。リスト 7 のコードをコンパイルして実行した結果は、以下のとおりです。

; ModuleID = 'top'
declare void @main()

main が実行するはずの一連の命令はまだ定義していません。そこで、これから基本ブロックを定義して main メソッドに関連付けます。基本ブロックは、LLVM IR の命令を集めたもので、そのコンストラクチャーの一部としてラベル (C のラベルと同様) を定義するオプションがあります。builder.setInsertPoint が、LLVM エンジンに対して次に命令を挿入する場所を指示します。リスト 8 にコードを記載します。

リスト 8. 基本ブロックを main に追加する
#include "llvm/LLVMContext.h"
#include "llvm/Module.h"
#include "llvm/Support/IRBuilder.h"

int main()
{
  llvm::LLVMContext& context = llvm::getGlobalContext();
  llvm::Module *module = new llvm::Module("top", context);
  llvm::IRBuilder<> builder(context); 

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

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

  module->dump( );
}

リスト 8 の出力を以下に記載します。main の基本ブロックが定義されているため、LLVM ダンプは main を宣言としてではなく、メソッド定義として扱うことに注目してください。何と賢いことでしょう!

; ModuleID = 'top'
define void @main() { 
entrypoint: 
}

今度はグローバル hello-world 文字列をコードに追加します。リスト 9 にコードを記載します。

リスト 9. グローバル文字列を LLVM モジュールに追加する
#include "llvm/LLVMContext.h"
#include "llvm/Module.h"
#include "llvm/Support/IRBuilder.h"

int main()
{
  llvm::LLVMContext& context = llvm::getGlobalContext();
  llvm::Module *module = new llvm::Module("top", context);
  llvm::IRBuilder<> builder(context); 

  llvm::FunctionType *funcType = 
      llvm::FunctionType::get(builder.getVoidTy(), false);
  llvm::Function *mainFunc = 
      llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "main", module);

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

  llvm::Value *helloWorld = builder.CreateGlobalStringPtr("hello world!\n");

  module->dump( );
}

リスト 9 による出力では、LLVM エンジンが文字列を以下のように出力することに注目してください。

; ModuleID = 'top'
@0 = internal unnamed_addr constant [14 x i8] c"hello world!\0A\00"
define void @main() {
entrypoint:
}

残る作業は、puts メソッドを宣言して呼び出すだけです。puts メソッドを宣言するには、このメソッドに適切な FunctionType* を作成する必要があります。当初の Hello World コードでご存知のとおり、putsi32 を返し、i8* を入力引数として受け入れます。リスト 10 に、puts の適切な型を作成するコードを記載します。

リスト 10. puts メソッドを宣言するコード
  std::vector<llvm::Type *> putsArgs;
  putsArgs.push_back(builder.getInt8Ty()->getPointerTo());
  llvm::ArrayRef<llvm::Type*>  argsRef(putsArgs);

  llvm::FunctionType *putsType = 
    llvm::FunctionType::get(builder.getInt32Ty(), argsRef, false);
  llvm::Constant *putsFunc = module->getOrInsertFunction("puts", putsType);

FunctionType::get には最初の引数として戻り値の型を渡し、2 番目の引数として LLVM::ArrayRef 構造を、最後の引数として false を渡しています。false は、この後には可変数の引数が続かないことを示します。ArrayRef 構造は、データが含まれていないことを除き、ベクトルと同様です。この ArrayRef 構造は配列やベクトルと同じく、主にデータ・ブロックをラップするために使用されます。以上の変更を加えた後、出力はリスト 11 のようになります。

リスト 11. puts メソッドを宣言する
; ModuleID = 'top'
@0 = internal unnamed_addr constant [14 x i8] c"hello world!\0A\00"
define void @main() {
entrypoint:
}
declare i32 @puts(i8*)

後は、puts メソッドを main の中で呼び出して、main から戻れば完成です。キャストおよび残りの処理は、LLVM API が引き受けてくれます。puts を呼び出すには、builder.CreateCall を呼び出せば良いのです。最後に、復帰命令を作成するために、builder.CreateRetVoid を呼び出します。リスト 12 に、完全に機能するコード全体を記載します。

リスト 12. 「Hello World」を出力する完全なコード
#include "llvm/ADT/ArrayRef.h"
#include "llvm/LLVMContext.h"
#include "llvm/Module.h"
#include "llvm/Function.h"
#include "llvm/BasicBlock.h"
#include "llvm/Support/IRBuilder.h"
#include <vector>
#include <string>

int main()
{
  llvm::LLVMContext & context = llvm::getGlobalContext();
  llvm::Module *module = new llvm::Module("asdf", context);
  llvm::IRBuilder<> builder(context);

  llvm::FunctionType *funcType = llvm::FunctionType::get(builder.getVoidTy(), false);
  llvm::Function *mainFunc = 
    llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "main", module);
  llvm::BasicBlock *entry = llvm::BasicBlock::Create(context, "entrypoint", mainFunc);
  builder.SetInsertPoint(entry);

  llvm::Value *helloWorld = builder.CreateGlobalStringPtr("hello world!\n");

  std::vector<llvm::Type *> putsArgs;
  putsArgs.push_back(builder.getInt8Ty()->getPointerTo());
  llvm::ArrayRef<llvm::Type*>  argsRef(putsArgs);

  llvm::FunctionType *putsType = 
    llvm::FunctionType::get(builder.getInt32Ty(), argsRef, false);
  llvm::Constant *putsFunc = module->getOrInsertFunction("puts", putsType);

  builder.CreateCall(putsFunc, helloWorld);
  builder.CreateRetVoid();
  module->dump();
}

まとめ

この連載の他の記事

連載「LLVM フレームワークで実用的なコンパイラーを作成する」の他の記事を見る

この LLVM の導入編では、llillvm-config などの LLVM ツールについて学び、LLVM 中間コードの内容を調べた後、LLVM API を使用して中間コードを自動的に生成しました。この連載の最終回となる第 2 回では、LLVM を利用することができる別の新しいタスクを詳しく探ります。それは、必要最小限の作業で別のコンパイル・パスを追加することです。

参考文献

学ぶために

製品や技術を入手するために

  • ご自分に最適な方法で IBM 製品を評価してください。評価の方法としては、製品の試用版をダウンロードすることも、オンラインで製品を試してみることも、クラウド環境で製品を使用することもできます。また、SOA Sandbox では、数時間でサービス指向アーキテクチャーの実装方法を効率的に学ぶことができます。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。プロフィールで選択した情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source, Linux
ArticleID=823511
ArticleTitle=LLVM フレームワークで実用的なコンパイラーを作成する: 第 1 回
publish-date=07052012