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
.