私の最初のLLVMコンパイラ

このチュートリアルは、LLVMで最も単純なコンパイラーを作成することに関するものです。 事前の準備は必要ありません。







コンパイラの入力言語はBFになります。 これはコンパイラ用の古典的な「おもちゃ」言語であり、LLVMの例 BFコンパイラもあります! この投稿では、コンパイラの記述プロセスを説明付きで説明します。



最初のLLVMプログラム



LLVMフレームワークは、 LLVM IRプログラミング言語を中心に構築されています。 最初に、可能な限り最も単純なLLVM IRプログラムを作成します。



LLVMはすでにLLVM IRを使用しているため、 clangを使用して単純なプログラムがどのように見えるかを確認できます。 簡単なCプログラムを使用する



int main() { return 42; }
      
      





clangに渡します:



 # -O3 ensures we discard any unnecessary instructions. $ clang -S -emit-llvm -O3 forty_two.c
      
      





次のようなファイルforty_two.llを取得します。



 define i32 @main() { ret i32 42 }
      
      





初めてのLLVM IRプログラム! lliを使用して実行できます。



 $ lli forty_two.ll $ echo $? 42
      
      





スケルトンを書く



BFコマンドをLLVM IR命令シーケンスにコンパイルします。 ただし、LLVMがエントリポイントの場所を認識できるように、これらの命令はmain関数内に配置する必要があります。 また、メモリセルとセルインデックスを割り当てて初期化する必要があります。



繰り返しになりますが、Cで同等のものを記述し、Clangが生成するLLVM IRコードを確認します。 使用するプログラムのスケルトンは次のとおりです。



 declare i8* @calloc(i32, i32) declare void @free(i8*) define i32 @main() { ; Allocate 30,000 cells on the heap. %cells = call i8* @calloc(i32 30000, i32 1) ; Allocate a stack variable to track the cell index. %cell_index_ptr = alloca i32 ; Initialise it to zero. store i32 0, i32* %cell_index_ptr ;;;;;;;;;;;;;;;;;;;; ; Our BF code will go here! ;;;;;;;;;;;;;;;;;;;; ; Free the memory for the cells. call void @free(i8* %cells) ret i32 0 }
      
      





手動コンパイル>



「>」は、最も簡単なBFコマンドです。 テキストエディタを開き、LLVM IRで同等の機能を記述します。



BFの知識が少し錆びている場合、「>」はセルインデックスを単純にインクリメントします。



 %cell_index = load i32* %cell_index_ptr %new_cell_index = add i32 1, %cell_index store i32 %new_cell_index, i32* %cell_index_ptr
      
      





コードが正しいことを確認するには、実行します。 それをスケルトンに挿入し 、lliで実行して、何が起こったかを見てください。 「>」の実装は簡単で、「<」のプログラムも作成します。



手動コンパイル+



BF "+"コマンドは、現在のセルをインクリメントします。 セルポインターを逆参照し、新しい値を計算して保存する必要があります。 Cでは、次のようになります。



 char *cell_ptr = cells + cell_index; char current_value = *cell_ptr; char new_value = current_value + 1; *cell_ptr = new_value;
      
      





LLVMはgetelementptrステートメントを使用してポインターを計算します。 コードは次のようになります。



 %cell_index = load i32* %cell_index_ptr %cell_ptr = getelementptr i8* %cells, i32 %cell_index %current_value = load i8* %cell_ptr %new_value = add i8 %current_value, 1 store i8 %new_value, i8* %cell_ptr
      
      





再度、スケルトンプログラムを配置してこれをテストし 「-」についても同じことを行います。



入出力



BFには2つのI / Oコマンドがあります。「、」は標準入力からセルに読み取ります。「。」 セルからstdoutに書き込みます。 このためにC関数を呼び出す必要があります: putchargetchar



以前にmallocで行ったように、これらの関数を宣言する必要があります。



 declare i32 @putchar(i32) declare i32 @getchar()
      
      





コマンドを実装するには、 getcharを呼び出し、結果をcharにトリミングし、現在のセルに書き込みます。



 %cell_index = load i32* %cell_index_ptr %cell_ptr = getelementptr i8* %cells, i32 %cell_index %input_int = call i32 @getchar() %input_byte = trunc i32 %input_int to i8 store i8 %input_byte, i8* %cell_ptr
      
      





「。」 これは逆の順序で機能します。セルから読み取り、署名された拡張機能を作成し、 putcharを呼び出します。



 %cell_index = load i32* %cell_index_ptr %cell_ptr = getelementptr i8* %cells, i32 %cell_index %current_cell = load i8* %cell_ptr %current_cell_word = sext i8 %current_cell to i32 call i32 @putchar(i32 %current_cell_word)
      
      





サイクル



LLVM IR命令はベースユニットに編成されています。 ブロック内で遷移のない命令のシーケンス。 各ベースユニットの終わりに、別のベースユニットに切り替えるか、リターンを完了する必要があります。



「[x] y」をコンパイルするには、現在のセルを確認してから、ループ本体のxまたはyに移動する必要があります。 xの終わりに、最初に行く必要があります。



 loop_header: %cell_index = load i32* %cell_index_ptr %cell_ptr = getelementptr i8* %cells, i32 %cell_index %cell_value = load i8* %cell_ptr %is_zero = icmp eq i8 %cell_value, 0 br i1 %is_zero, label %loop_after, label %loop_body loop_body: ; x br label %loop_header loop_after: ; y
      
      





再帰があることに注意してください。xにはループも含まれる場合があります。 サンプルプログラムはこちらです。



すべてをまとめる



がんばりました! 現在、LLVM APIを使用して命令を生成しています。 各IR命令には、ベースユニットに追加できる対応するC ++オブジェクトがあります。



LLVM APIには、便利なIRBuilderコンセプトもあります。 IRBuilderクラスは、すべてのIR命令を作成するためのメソッドを提供し、IR生成を簡単にします。



「>」のLLVM IRを生成するC ++コードは次のとおりです。 優れたLLVM チュートリアルには、LLVM APIを使用して簡単なC ++プログラムをコンパイルするための手順が含まれています。



 BasicBlock *BB; // Instantiate an IRBuilder that appends to our // current basic block. IRBuilder<> Builder(getGlobalContext()); Builder.SetInsertPoint(BB); // We want to increment by 1, but since cell_index is // 32-bit, our constant must be 32-bit too. Value *IncrementAmount = ConstantInt::get(getGlobalContext(), APInt(32, 1)); // Emit the load, add and store instructions. Value *CellIndex = Builder.CreateLoad(CellIndexPtr, "cell_index"); Value *NewCellIndex = Builder.CreateAdd(CellIndex, IncrementAmount, "new_cell_index"); Builder.CreateStore(NewCellIndex, CellIndexPtr);
      
      





他のBFコマンドのコンパイルは、手書きスニペットの簡単な翻訳です。 ここで完全に機能するコンパイラを見ることができます



マシンコード生成



コンパイラはLLVM IRのみを生成します。 実際のコンパイラはマシンコードを生成します。 llcを使用してオブジェクトファイルに変換し、リンクして実行可能ファイルを取得します。



 $ ./compiler hello_world.bf $ llc -filetype=obj hello_world.ll $ gcc hello_world.o -o hello_world $ ./hello_world Hello World!
      
      





以上です!



アプリケーション:最適化



最適化を実行して、BFプログラムからより高速な実行可能ファイルを取得できます。 ただし、LLVMは、シンプルでループのないBFプログラムを最適なLLVM IRビューにコンパイルするのに十分スマートです!



 $ cat exclamation.bf +++++ +++++ +++++ +++++ +++++ +++++ +++ ASCII 33 is '!' . Write ! to stdout $ ./compiler exclamation.bf $ opt -S -O3 exclamation.ll -o optimised_exclamation.ll
      
      





取得するもの:



 define i32 @main() { entry: %0 = tail call i32 @putchar(i32 33) ret i32 0 }
      
      






All Articles