コンパイル。 5および1/2:バックエンドとしてのllvm

tyomitch "Compilation"による一連の記事( ここここここここここここここ )では、 4部で説明されているjskおもちゃ言語の翻訳者の構築が考慮されました
このトランスレーターのバックエンドとして、 tyomitchはこのバイトコードのバイトコード実装とインタープリターを提案しました。

私の意見では、より合理的なアプローチはバックエンドに既存のソリューション、例えばllvmを使用することであり、「具体的な文のない批評-批評」の原則に従って、llvmを使用したこの小さなjsk言語の実装オプションを提案します。

これはjskに何を与えますか? このコンパイル、つまり、結果は、ランタイム、深刻な最適化の可能性、コードプロファイリングに依存しない実行可能ファイルであり、バックエンドのドキュメントを自動的に取得します(メンテナンスが容易になります)。

フロントエンド開発者の観点からllvmはどのように機能しますか?

llvmが機能するには、コンテキストを作成し、それを使用してモジュールを作成する必要があります。 各モジュールにはいくつかの機能があります。 jskは関数をサポートしていないため、1つの関数main()を作成します。
各関数には複数のコードブロックを含めることができます。
コードブロックとは何ですか?

多くの場合、プログラムでは、まだ定義されていないラベルに制御を転送する必要が生じます。 libjitでは、最初にラベルを作成して使用し、後で定義することができます。Gnullightningでは、コントロール転送をコードに記述し、後でこのコードにパッチを適用して目的のアドレスを置き換えることができます。

つまり、llvm APIはラベルなどのことをまったく知りません。 制御を転送する必要がある場合、コードブロックへのリンクがコマンドに渡されます。 たとえば、建設用

if then else endif


LLVM APIでは、thenブランチのコードブロック、elseブランチのコードブロックを作成し、これらのブロックを条件付きブランチコマンドで指定する必要があります。 のようなもの

BasicBlock *ThenBB = BasicBlock::Create(context, "");
BasicBlock *ElseBB = BasicBlock::Create(context, "");
BasicBlock *AfterIfBB = BasicBlock::Create(context, "");
Builder.CreateCondBr(CondV, ThenBB, ElseBB);

ThenBB, ElseBB, AfterIfBB.


実際のコード生成には、すべてのLLVMコマンドを生成する(大まかに言って)メソッドを含むIRBuilderが使用されます。

すべてのコードを作成したら、次は何をしますか?


これは当社の裁量によるものです。 llvmモジュールのdump()メソッドを使用して受信したバイトコードを読み取り可能な形式で印刷するか、コードを実行する(つまり、llvmをJITコンパイラーとして使用する)か、より正確に言えば、バイトコードをファイルに保存します。 これにより、このバイトコードをさらに最適化し、標準のllvmツールを使用して実行可能ファイルに変換できます。

だから

目標が定義され、タスクが設定され、仕事仲間が


すべてのコードをここに配置することはできません。誰かがソースを使用したい場合は、アーカイブをこちらまたはこちらから入手できます
まず、ソーステキストに特定のヘッダーを含める必要があります。

#include "llvm / DerivedTypes.h"
#include "llvm / LLVMContext.h"
#include "llvm / Module.h"
#include "llvm / Analysis / Verifier.h"
#include "llvm / Support / IRBuilder.h"
#include <llvm / Support / raw_ostream.h>
#include <llvm / Bitcode / ReaderWriter.h>
名前空間 llvm を使用し ます


それで十分でしょう。 次に、コンテキスト、モジュールを初期化し、メイン関数とIRBuilderを作成して関数にコードを追加します

宣言では:

静的モジュール* TheModule ;
static IRBuilder <> Builder getGlobalContext ;


コードの始まりは次のとおりです。

LLVMContext Context = getGlobalContext ;
TheModule = new Module "jsk" 、Context ;
const Type * voidType = Type :: getVoidTy Context ;

func_main = cast < Function > TheModule- > getOrInsertFunction "main" 、voidType、 NULL ;
func_main- > setCallingConv CallingConv :: C ;
BasicBlock * block = BasicBlock :: Create getGlobalContext "code" 、func_main ;
ビルダー SetInsertPoint ブロック ;



コードを生成したら、ret voidを生成してダミー関数を終了し、バイトコードをa.out.bcファイルにダンプします

ビルダー CreateRetVoid ;

std :: string ErrStr ;
raw_fd_ostreamビットコード "a.out.bc" 、ErrStr、 0 ;
WriteBitcodeToFile TheModule、bitcode ;
ビットコード。 閉じる ;


次に、jsk言語の特定の操作用のコードの生成について

変数


変数ごとに、命令を使用してスタック上の場所を予約します

%varname = alloca i32

または、APIの観点から

Builder.CreateAlloca(IntegerType::get(context,32), 0, VarName.c_str());

問題は、初めて変数に出会った正確な場所をスタックに割り当てることができないことです。 変数がwhileループの途中で発生した場合、変数のコピーでスタック全体が台無しになるためです。

したがって、すでに定義されている変数のリストが必要です。テキスト内の変数に出会ったときは、チェックし、初めて発生する場合は、そのメモリ割り当てを関数コードの一番上に配置します。 つまり、現在の関数から最初のブロックを取得し、このブロックの上にそこにallocaコマンドを追加します。 幸いなことに、llvmを使用すると、ブロックの最後だけでなく、ブロックの最初にもコマンドを書くことができます。 これがどのように行われるかは、ソースコード-CreateEntryBlockAlloca()関数で確認できます。

変数の割り当て

Value *result = value->emit();
Builder.CreateStore(result,varreference);

..

store i32 %result, i32* %varreference


したがって、変数の値を取得します


return Builder.CreateLoad(varref);

%val = load i32* %varref


バイナリおよび単項演算。


ここは簡単です


スイッチ op {
ケース '+' Builderを返します。 CreateAdd L、R ;
ケース '-' Builderを返します。 CreateSub L、R ;
ケース '*' Builderを返します。 CreateMul L、R ;
ケース '/' Builderを返します。 CreateSDiv L、R ;
ケース '<' tmp =ビルダー。 CreateICmpSLT L、R ; Builderを返します。 CreateZExt tmp、IntegerType :: get getGlobalContext 32 ;
ケース '>' tmp =ビルダー。 CreateICmpSGT L、R ; Builderを返します。 CreateZExt tmp、IntegerType :: get getGlobalContext 32 ;
ケース 'L' tmp =ビルダー。 CreateICmpSLE L、R ; Builderを返します。 CreateZExt tmp、IntegerType :: get getGlobalContext 32 ;
ケース 'G' tmp =ビルダー。 CreateICmpSGE L、R ; Builderを返します。 CreateZExt tmp、IntegerType :: get getGlobalContext 32 ;
ケース 'N' tmp =ビルダー。 CreateICmpNE L、R ; Builderを返します。 CreateZExt tmp、IntegerType :: get getGlobalContext 32 ;
ケース '=' tmp =ビルダー。 CreateICmpEQ L、R ; Builderを返します。 CreateZExt tmp、IntegerType :: get getGlobalContext 32 ;

デフォルト ErrorV 「無効な二項演算子」 )を 返し ます
}


IFおよびWHILE


すでに説明したように、コードブロックを使用します。 IFの例:

* CondV = cond- > emit ;
//条件式を0と比較します
CondV =ビルダー。 CreateICmpNE CondV、zero、 "ifcond" ;
// IF分岐のコードブロック
BasicBlock * ThenBB = BasicBlock :: Create getGlobalContext "thenblock" ;
BasicBlock * ElseBB = BasicBlock :: Create getGlobalContext "elseblock" ;
BasicBlock * MergeBB = BasicBlock :: Create getGlobalContext "afterifblock" ;
// IFの実際の分岐
ビルダー CreateCondBr CondV、ThenBB、ElseBB ;

//次に、コードをTHENブランチに追加します
ビルダー SetInsertPoint ThenBB ;
これ- > thenops。 放出 ;
// THENの最後に「IFの後」に切り替えます
ビルダー CreateBr MergeBB ;

関数- > getBasicBlockList push_back ElseBB ;
//次に、ELSEのまぶたにコードを追加します
ビルダー SetInsertPoint ElseBB ;
これ- > elseops。 放出 ;
//他の最後に、「IF後」に移動します
ビルダー CreateBr MergeBB ;

関数- > getBasicBlockList push_back MergeBB ;
// IFの後に、MergeBBにのみコードを追加します
ビルダー SetInsertPoint MergeBB ;
ゼロを返します


一方で:

BasicBlock * CondBB = BasicBlock :: Create getGlobalContext "whilexpr" 、TheFunction ;
BasicBlock * LoopBB = BasicBlock :: Create getGlobalContext "loop" ;
BasicBlock * AfterBB = BasicBlock :: Create getGlobalContext "after" ;
ビルダー CreateBr CondBB ;

//条件ブロック。 ここにコードを追加します。 条件コードを生成し、結果を0と比較します
//そして、条件が満たされない場合-AfterBBに進みます
ビルダー SetInsertPoint CondBB ;
* CondV = cond- > emit ;
if CondV == 0 0を 返し ます
// 0と等しいことを比較して、条件をブールに変換します。
CondV =ビルダー。 CreateICmpNE CondV、zero、 "whilecond" ;
ビルダー CreateCondBr CondV、LoopBB、AfterBB ;


//ボディブロックwhile。 ここにボディコードを記述してから、WHILE条件への移行
関数- > getBasicBlockList push_back LoopBB ;
ビルダー SetInsertPoint LoopBB ;
* Ops = this- > ops。 放出 ;
ビルダー CreateBr CondBB ;


// 'After WHILE'をブロックします。現時点では、
//次のすべてのコードはこのブロックで記述する必要があること
関数- > getBasicBlockList push_back AfterBB ;
ビルダー SetInsertPoint AfterBB ;


最後に、コンパイラを試すことができます



#make
bison -d jsk.y
flex jsk.lex
g++ -O2 jsk.tab.c lex.yy.c `llvm-config --cxxflags --libs` -lrt -ldl -o jskc
jsk.tab.c: In function 'int yyparse()':
jsk.tab.c:2026: warning: deprecated conversion from string constant to 'char*'
jsk.tab.c:2141: warning: deprecated conversion from string constant to 'char*'
jsk.lex: In function 'int yylex()':
jsk.lex:34: warning: deprecated conversion from string constant to 'char*'
jsk.lex:35: warning: deprecated conversion from string constant to 'char*'
jsk.lex:39: warning: deprecated conversion from string constant to 'char*'


だから コンパイラの準備ができました。 いくつかのテストタスクで試してみましょう。 たとえば
a=b=88;
b=b+1;
echo("test4=",a," ",b,"\n");


雑草:

./jskc <test3.jsk

そして、現在のディレクトリにa.out.bcを取得します。 分解できます:

llvm-dis <a.out.bc
; ModuleID = ''

@.format1 = internal constant [3 x i8] c"%d\00" ; <[3 x i8]*> [#uses=1]
@.format2 = internal constant [3 x i8] c"%s\00" ; <[3 x i8]*> [#uses=1]
@0 = internal constant [7 x i8] c"test4=\00" ; <[7 x i8]*> [#uses=1]
@1 = internal constant [2 x i8] c" \00" ; <[2 x i8]*> [#uses=1]
@2 = internal constant [2 x i8] c"\0A\00" ; <[2 x i8]*> [#uses=1]

define void @main() {
code:
%a = alloca i32 ; <i32*> [#uses=2]
%b = alloca i32 ; <i32*> [#uses=4]
%int_for_scanf___ = alloca i32 ; <i32*> [#uses=0]
store i32 88, i32* %b
store i32 88, i32* %a
%0 = load i32* %b ; [#uses=1]
%1 = add i32 %0, 1 ; [#uses=1]
store i32 %1, i32* %b
call void (i8*, ...)* @printf(i8* getelementptr inbounds ([3 x i8]* @.format2, i32 0, i32 0), i8* getelementptr inbounds ([7 x i8]* @0, i32 0, i32 0))
%2 = load i32* %a ; [#uses=1]
call void (i8*, ...)* @printf(i8* getelementptr inbounds ([3 x i8]* @.format1, i32 0, i32 0), i32 %2)
call void (i8*, ...)* @printf(i8* getelementptr inbounds ([3 x i8]* @.format2, i32 0, i32 0), i8* getelementptr inbounds ([2 x i8]* @1, i32 0, i32 0))
%3 = load i32* %b ; [#uses=1]
call void (i8*, ...)* @printf(i8* getelementptr inbounds ([3 x i8]* @.format1, i32 0, i32 0), i32 %3)
call void (i8*, ...)* @printf(i8* getelementptr inbounds ([3 x i8]* @.format2, i32 0, i32 0), i8* getelementptr inbounds ([2 x i8]* @2, i32 0, i32 0))
ret void
}

declare void @printf(i8*, ...)

declare void @scanf(i8*, ...)

declare void @exit(i32)


lli , ( ) - :

$ llvmc a.out.bc

(!) a.out, , JSK :

$ ls -al ./a.out
-rwxr-xr-x 1 walrus walrus 4639 2010-08-25 16:49 ./a.out

$ file a.out
a.out: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.15, not stripped

$ ldd a.out
linux-gate.so.1 => (0x00762000)
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x00456000)
/lib/ld-linux.so.2 (0x00ee4000)


,
$ ./a.out
test4=88 89


.

Source: https://habr.com/ru/post/J102597/


All Articles