コンパイラアーキテクチャの王者LLVM――(6)マルチパス翻訳のマクロ翻訳システム
17890 ワード
LLVMプラットフォームは、わずか数年の間に、多くのプログラミング言語の行方を変え、多くの特色のあるプログラミング言語の出現を促した.
本文は西風逍遥旅行のオリジナルの文章で、転載は出典の西風の世界を明記してくださいhttp://blog.csdn.net/xfxyy_sxfancy
マルチパス翻訳マクロ翻訳システム
前回は文法ツリーを構築する基本モデルについて議論しましたが、Lex+Bison+Nodeを利用して、いくつかのコンポーネントが私たちの目標文法をAST文法ツリーに翻訳することができます.第4章では、RedAppleという実装型コンパイラの文法構造も示しました.では、私たちの準備は基本的な完成に基づいています.
AST構文ツリーの構築を完了した後、構文ツリー全体を遍歴し、LLVMのモジュールに翻訳して.bcバイトコードに出力するメカニズムが必要です.
このメカニズムは、文法ツリー全体を複数回スキャンし、必要な部分をスキャンするたびにモジュール全体を構築するため、複数のマクロ翻訳システムと呼ばれています.Javaのような構文特性を実現することを望んでいます.定義順序を考慮する必要はありません.定義すれば、この記号を見つけることができます.これには合理的なスキャン順序が必要です.
スキャン順序の決定
まず、タイプの宣言が重要なため、タイプ宣言がなければ関数を構築できません.次に、すべての関数をスキャンし、関数の宣言を構築します.最後に,すべての関数定義をスキャンし,各関数の関数体を構築した.
このように我々は3回のスキャンであり,効率の問題を心配する必要はない.前の2回のスキャンはいずれもルートノードの下の層であり,スキャンの要素が非常に少ないため,処理が速い.
スキャン対象AST構文ツリー
これは私たちが前に生成したAST文法ツリーで、構造はまだはっきりしているでしょう.我々が利用できる遍歴手段は,前回実装したnextポインタであり,現在のノードのデータを絶えず判断し,対応するコードを生成することである.
各文の意味を区別するために、各リストの先頭に翻訳マクロの名前を追加しました.この設計はlispを模倣したもので、マクロはコンパイラの関数に相当し、メタデータを処理し、それを対応する内容に翻訳します.
たとえば、このコード:
AST構文ツリーは次のとおりです.
スキャン中のコンテキスト
翻訳の過程で、LLVMContext変数、シンボルテーブル、マクロ定義テーブルなどの必要な情報が必要です.また、必要な情報を格納するために、自分でコンテキストクラスを実現する必要があります.コンテキストクラスは最初のスキャンの前に初期化する必要があります.
たとえば、翻訳で変数に遭遇した場合、その変数は一時的なものなのか、グローバルなものなのか.どのようなタイプで、シンボルテーブルに表現を格納する必要があります.また、現在翻訳されている文はどのマクロに属していますか.どのように翻訳しますか.これらの情報を保存するクラスが必要です.
そこで私たちはまず実現の話をしないで、インタフェースを書きます
マクロの登録
マクロは内部のとても重要な関数で、自身は1つのC関数のポインタで、マクロは唯一の名前があって、map表を通じて、そのマクロの対応する関数を探して、それからそれを呼び出して現在の文法のノードに対して解析を行います.
マクロ関数の定義:
私がluaに倣って設計したことを登録し、関数ポインタを配列に組織し、構造体に初期化します.
このように書くのは、私たちが一度に関数を導入して私たちのシステムに入るのを便利にするためです.関数ポインタは私はやはりCポインタを使うことに慣れています.一般的にC++のメンバーポインタを使うのを避けます.それは複雑すぎて、他のモジュールとリンクしにくいです.C++には標準ABIがありませんが、C言語はあります.
スキャンのブートを実現
スキャンは簡単です.現在のノードが文字列であり、マクロ定義で見つかった場合は、このマクロを呼び出して処理します.そうしないと、リストの化であれば、各マクロを再帰的に処理します.
マクロの検索はstlテンプレートライブラリのmapとstringを直接使用して、とても便利です.
このようにして、私たちはマクロ翻訳を導くことができます.では、何回も翻訳はどのように実現されていますか.実はとても簡単で、マクロ登録関数を使って現在のマクロを置き換えればいいので、翻訳ガイドを再実行して、何度も翻訳したのではないでしょうか.
本文は西風逍遥旅行のオリジナルの文章で、転載は出典の西風の世界を明記してくださいhttp://blog.csdn.net/xfxyy_sxfancy
マルチパス翻訳マクロ翻訳システム
前回は文法ツリーを構築する基本モデルについて議論しましたが、Lex+Bison+Nodeを利用して、いくつかのコンポーネントが私たちの目標文法をAST文法ツリーに翻訳することができます.第4章では、RedAppleという実装型コンパイラの文法構造も示しました.では、私たちの準備は基本的な完成に基づいています.
AST構文ツリーの構築を完了した後、構文ツリー全体を遍歴し、LLVMのモジュールに翻訳して.bcバイトコードに出力するメカニズムが必要です.
このメカニズムは、文法ツリー全体を複数回スキャンし、必要な部分をスキャンするたびにモジュール全体を構築するため、複数のマクロ翻訳システムと呼ばれています.Javaのような構文特性を実現することを望んでいます.定義順序を考慮する必要はありません.定義すれば、この記号を見つけることができます.これには合理的なスキャン順序が必要です.
スキャン順序の決定
まず、タイプの宣言が重要なため、タイプ宣言がなければ関数を構築できません.次に、すべての関数をスキャンし、関数の宣言を構築します.最後に,すべての関数定義をスキャンし,各関数の関数体を構築した.
このように我々は3回のスキャンであり,効率の問題を心配する必要はない.前の2回のスキャンはいずれもルートノードの下の層であり,スキャンの要素が非常に少ないため,処理が速い.
スキャン対象AST構文ツリー
これは私たちが前に生成したAST文法ツリーで、構造はまだはっきりしているでしょう.我々が利用できる遍歴手段は,前回実装したnextポインタであり,現在のノードのデータを絶えず判断し,対応するコードを生成することである.
各文の意味を区別するために、各リストの先頭に翻訳マクロの名前を追加しました.この設計はlispを模倣したもので、マクロはコンパイラの関数に相当し、メタデータを処理し、それを対応する内容に翻訳します.
たとえば、このコード:
void hello(int k, int g) {
int y = k + g;
printf("%d
", y);
if (k + g < 5) printf("right
");
}
void go(int k) {
int a = 0;
while (a < k) {
printf("go-%d
", a);
a = a + 1;
}
}
void print(int k) {
for (int i = 0; i < 10; i = i+1) {
printf("hello-%d
",i);
}
}
void main() {
printf("hello world
");
hello(1,2);
print(9);
}
AST構文ツリーは次のとおりです.
Node
Node
String function
String void
String hello
Node
Node
String set
String int
String k
Node
String set
String int
String g
Node
Node
String set
String int
String y
Node
String opt2
String +
ID k
ID g
Node
String call
String printf
String %d
ID y
Node
String if
Node
String opt2
String <
Node
String opt2
String +
ID k
ID g
Int 5
Node
String call
String printf
String right
Node
String function
String void
String go
Node
Node
String set
String int
String k
Node
Node
String set
String int
String a
Int 0
Node
String while
Node
String opt2
String <
ID a
ID k
Node
Node
String call
String printf
String go-%d
ID a
Node
String opt2
String =
ID a
Node
String opt2
String +
ID a
Int 1
Node
String function
String void
String print
Node
Node
String set
String int
String k
Node
Node
String for
Node
String set
String int
String i
Int 0
Node
String opt2
String <
ID i
Int 10
Node
String opt2
String =
ID i
Node
String opt2
String +
ID i
Int 1
Node
Node
String call
String printf
String hello-%d
ID i
Node
String function
String void
String main
Node
Node
Node
String call
String printf
String hello world
Node
String call
String hello
Int 1
Int 2
Node
String call
String print
Int 9
スキャン中のコンテキスト
翻訳の過程で、LLVMContext変数、シンボルテーブル、マクロ定義テーブルなどの必要な情報が必要です.また、必要な情報を格納するために、自分でコンテキストクラスを実現する必要があります.コンテキストクラスは最初のスキャンの前に初期化する必要があります.
たとえば、翻訳で変数に遭遇した場合、その変数は一時的なものなのか、グローバルなものなのか.どのようなタイプで、シンボルテーブルに表現を格納する必要があります.また、現在翻訳されている文はどのマクロに属していますか.どのように翻訳しますか.これらの情報を保存するクラスが必要です.
そこで私たちはまず実現の話をしないで、インタフェースを書きます
class CodeGenContext;
typedef Value* (*CodeGenFunction)(CodeGenContext*, Node*);
typedef struct _funcReg
{
const char* name;
CodeGenFunction func;
} FuncReg;
class CodeGenContext
{
public:
CodeGenContext(Node* node);
~CodeGenContext();
//
void PreInit();
void PreTypeInit();
void Init();
void MakeBegin() {
MacroMake(root);
}
// Node
Value* MacroMake(Node* node);
//
void MacroMakeAll(Node* node);
CodeGenFunction getMacro(string& str);
// C++
// void AddMacros(const FuncReg* macro_funcs); //
void AddOrReplaceMacros(const FuncReg* macro_funcs);
//
BasicBlock* getNowBlock();
BasicBlock* createBlock();
BasicBlock* createBlock(Function* f);
//
Function* getFunction(Node* node);
Function* getFunction(std::string& name);
void nowFunction(Function* _nowFunc);
void setModule(Module* pM) { M = pM; }
Module* getModule() { return M; }
void setContext(LLVMContext* pC) { Context = pC; }
LLVMContext* getContext() { return Context; }
//
void DefType(string name, Type* t);
Type* FindType(string& name);
Type* FindType(Node*);
void SaveMacros();
void RecoverMacros();
bool isSave() { return _save; }
void setIsSave(bool save) { _save = save; }
id* FindST(Node* node) const;
id* FindST(string& str) const {
return st->find(str);
}
IDTable* st;
private:
//
Node* root;
// LLVM Module
Module* M;
LLVMContext* Context;
Function* nowFunc;
BasicBlock* nowBlock;
//
map<string, CodeGenFunction> macro_map;
//
stack<map<string, CodeGenFunction> > macro_save_stack;
void setNormalType();
//
bool _save;
};
マクロの登録
マクロは内部のとても重要な関数で、自身は1つのC関数のポインタで、マクロは唯一の名前があって、map表を通じて、そのマクロの対応する関数を探して、それからそれを呼び出して現在の文法のノードに対して解析を行います.
マクロ関数の定義:
typedef Value* (*CodeGenFunction)(CodeGenContext*, Node*);
私がluaに倣って設計したことを登録し、関数ポインタを配列に組織し、構造体に初期化します.
extern const FuncReg macro_funcs[] = {
{"function", function_macro},
{"struct", struct_macro},
{"set", set_macro},
{"call", call_macro},
{"opt2", opt2_macro},
{"for", for_macro},
{"while", while_macro},
{"if", if_macro},
{"return", return_macro},
{"new", new_macro},
{NULL, NULL}
};
このように書くのは、私たちが一度に関数を導入して私たちのシステムに入るのを便利にするためです.関数ポインタは私はやはりCポインタを使うことに慣れています.一般的にC++のメンバーポインタを使うのを避けます.それは複雑すぎて、他のモジュールとリンクしにくいです.C++には標準ABIがありませんが、C言語はあります.
スキャンのブートを実現
スキャンは簡単です.現在のノードが文字列であり、マクロ定義で見つかった場合は、このマクロを呼び出して処理します.そうしないと、リストの化であれば、各マクロを再帰的に処理します.
マクロの検索はstlテンプレートライブラリのmapとstringを直接使用して、とても便利です.
Value* CodeGenContext::MacroMake(Node* node) {
if (node == NULL) return NULL;
if (node->isStringNode()) {
StringNode* str_node = (StringNode*)node;
CodeGenFunction func = getMacro(str_node->getStr());
if (func != NULL) {
return func(this, node->getNext());
}
return NULL;
}
if (node->getChild() != NULL && node->getChild()->isStringNode())
return MacroMake(node->getChild());
Value* ans;
for (Node* p = node->getChild(); p != NULL; p = p->getNext())
ans = MacroMake(p);
return ans;
}
CodeGenFunction CodeGenContext::getMacro(string& str) {
auto func = macro_map.find(str);
if (func != macro_map.end()) return func->second;
else return NULL;
}
このようにして、私たちはマクロ翻訳を導くことができます.では、何回も翻訳はどのように実現されていますか.実はとても簡単で、マクロ登録関数を使って現在のマクロを置き換えればいいので、翻訳ガイドを再実行して、何度も翻訳したのではないでしょうか.