青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

兔子的技術博客

兔子

   :: 首頁 :: 聯系 :: 聚合  :: 管理
  202 Posts :: 0 Stories :: 43 Comments :: 0 Trackbacks

留言簿(10)

最新評論

閱讀排行榜

評論排行榜



1、介紹

我總是對編譯器和語言非常感興趣,但是興趣并不會讓你走的更遠。大量的編譯器的設計概念可以搞的任何一個程序員迷失在這些概念之中。不用說,我也曾 今嘗試過,但是并沒有取得太大的成功,我以前的嘗試都停留在語義分析階段。本文的靈感主要來源于我最近一次的嘗試,并且在這一次中我取得一點成就。

幸運的是,最近的幾年,我參加了一些項目,這些項目給了我在建立編譯器上很多有用的經驗和觀點。另外一件事是,我非常幸運得到LLVM的幫助。對于這個工具,我不知道改怎么去形容它,但是他給我的這個編譯器的確帶來非常大的幫助。

1.1、你為什么要閱讀本文

你也許想看看我正在做的事情,但是更有可能的是,你也是和我一樣對編譯器和語言非常感興趣,并且也可能遇到了一些在探索的過程中遇到了一些難題,你 可能正打算解決這些難題,但是卻沒有發現好的資源。本文的目標就是提供這些資源,并以一種手把手的方式教你從頭到尾的去創建一個具有基本功能的語言編譯 器。

在本文,我不會去解釋一些編譯器基本理論,所以你要在開始本文前去了解什么是BNF語法,什么是抽象語法樹數據結構 AST data structure,什么是基礎編譯器流水線 complier pipline。就是說,我會把本文描述的盡量簡單。本文的目的就是以一種簡單易懂的方式來介紹相關編譯器資源的方式來幫助那些從來沒有編譯器經驗的人。

1.2、達到的成果

如果你根據文章內容一步步來,你將會得到一個能定義函數,調用函數,定義變量,給變量賦值執行基本數學操作的語言。這門語言支持兩種基本類 型,double和integer類型。還有一些功能還未實現,因此,你可以通過自己去實現這些功能得到你滿意的功能并且能為你理解編寫一個編譯器提供不 少的幫助。

1.3 問題解答

1.3.1 我需要了解什么樣的語言

我們使用的工具是基于C/C++的。LLVM是基于C++的,我們的這個語言也基于C++,因為C++具有很多面向對象的優點和可以被重用的 STL。此外對于C,Lex和Bison都具有那些初看起來令人迷惑的語法,但是我將盡可能的去解釋他。我們需要處理的語法非常小,最多就100行,因此 它是比較容易理解的。

1.3.2 很復雜嗎?

是或否,這里面有很多的東西你需要了解,甚至多的讓人感覺到害怕,但是老實說,其實這些都非常簡單,我們同樣會使用很多工具分解這些層次的復雜性,并使得這些內容可管理。

1.3.3 完成它需要多長時間

我們將要完成的編譯器花了我三天的時間。但是如果你按“follow me”的方式來完成這個編譯器的話,你將會花費更少的時間。如果要全部理解這里面的內容可能會花去稍微長一點的時間,但是你至少應該在一個下午就將整個編譯器運行起來。

好,如果你已經準備好,我們開始吧。

2、準備開始

2.1 構成編譯器的最基本的要素

一個編譯器是由一組有三個到四個組件(還有一些子組件)構成,數據以管道的方式從一個組件輸入并流向下一個組件。在我們這個編譯器中,可能會用到一些稍微不同的工具。下面這個圖展示了我們構造一個編譯器的步驟,和每個步驟中將使用的工具。

Compiler pipeline

從上圖你可以看到在Linking這一步是灰掉的。我們的語言將不支持編譯器的連接(很多的語言都不支持編譯器的連接)。在文法分析階段,我們將使用開源工具Lex,即如今的Flex,文法分析一般都伴隨者語法分析,我們使用的語法分析工具將會是Yacc,或者說是Bison,最后一旦語義分析完成,我們將遍歷我們的抽象語法樹,并生成我們的”bytecode 字節碼”,或”機器碼 matchine code”。做這一步,我們將使用LLVM,它能生成快速字節碼,我們將使用LLVM的JIT(Just In Tinme)來在我們的機器上編譯執行它

總結一下,步驟如下:

  1. 文法分析用Flex:將數據分隔成一個個的標記token (標示符identifiers,關鍵字keywords,數字numbers, 中括號brackets, 大括號braces, 等等etc.)
  2. 語法分析用Bison: 在分析標記的時候生成抽象語法樹. Bison 將會做掉幾乎所有的這些工作, 我們定義好我們的抽象語法樹就OK了.
  3. 組裝用LLVM: 這里我們將遍歷我們的抽象語法樹,并未每一個節點生成字節/機器碼。 這聽起來似乎很瘋狂,但是這幾乎就是最簡單的 一步了.

在我們開始下一步之前,你應該準備安裝好Flex,Bison和LLVM。因為我們馬上就要使用到它們。

2.2 定義我們的語法

我們語法是我們語言中最核心的部分,我們的語法使用類似標準C的語法,因為這樣的語法非常熟悉,而且簡單。我們語法的一個典型的例子如下:

1
2
3
4
5
int do_math(int a) {
  int x = a * 5 + 3
}
 
do_math(10)

看起來很簡單。它和C非常相似,但是它沒有使用分號做語句的分隔,同時你也會注意到我們的語法中沒有return語句。這就是你可以自己實現的部分。

現在我們還沒有任何機制來驗證結果。我們將通過檢查我們編譯之后LLVM打印出的字節碼驗證我們程序的正確性。

3、 第一步,使用Flex進行文法分析

這是最簡單的一步,給定語法之后,我們需要將我們的輸入轉換一系列內部標記token。如前所述,我們的語法具有非常基礎的標記token:標示符 identifier ,數字常量(整型和浮點型),數學運算符號,括號,中括號,我們的文法定義文件稱為token.l,它具有一些固定的語法。定義如下:

%{
#include
#include "node.h"
#include "parser.hpp"
#define SAVE_TOKEN yylval.string = new std::string(yytext, yyleng)
#define TOKEN(t) (yylval.token = t)
extern "C" int yywrap() { }
%}

%%

[ \t\n] ;
[a-zA-Z_][a-zA-Z0-9_]* SAVE_TOKEN; return TIDENTIFIER;
[0-9]+\.[0-9]* SAVE_TOKEN; return TDOUBLE;
[0-9]+ SAVE_TOKEN; return TINTEGER;
"=" return TOKEN(TEQUAL);
"==" return TOKEN(TCEQ);
"!=" return TOKEN(TCNE);
"<" return TOKEN(TCLT);
"<=" return TOKEN(TCLE);
">" return TOKEN(TCGT);
">=" return TOKEN(TCGE);
"(" return TOKEN(TLPAREN);
")" return TOKEN(TRPAREN);
"{" return TOKEN(TLBRACE);
"}" return TOKEN(TRBRACE);
"." return TOKEN(TDOT);
"," return TOKEN(TCOMMA);
"+" return TOKEN(TPLUS);
"-" return TOKEN(TMINUS);
"*" return TOKEN(TMUL);
"/" return TOKEN(TDIV);
. printf("Unknown token!\n"); yyterminate();

%%

在第一節(譯者注:即%{%}中定義的部分)聲明了一些特定的C代碼。由于Bison不會去訪問我門的yytext變量,我們使用 宏”SAVE_TOKEN”來保證標示符的文本和文本長度是安全的(而不是靠標記本身來保證)。第一個token告訴我們要忽略掉那些空白字符。你會注意 到我們有些一些等價性比較的標記和其他。還有一些標記還沒有實現,你可以非常自由的將這些標記加到你自己的編譯器中去。

現在我們在這里做的是定義這些標記和他們的符號名。符號(比如TIDENTFIER)將成為我們語法中的終結符。我們只是返回它,我們從未定義它, 他們是在什么地方定義的?當然是在bison語法文件中。我們包含的parser.hpp頭文件將會被bison所生成,并且里面的所有符號都將被生成, 并被我們在這里使用。

我們對這個token.l運行flex命令,并生成tokens.cpp文件,這個程序將會和我們的語法分析器一起編譯并提供yylex()函數來識別這些標記。我們將在稍后運行這個命令,因為現在我們需要從bison那里生成頭文件。

4、第2步 使用Bison進行語法分析

這是我們工作中最富有挑戰性的一部分。生成一個正確的無二義的語法并不是一項簡單的工作,要經過很多實踐努力。慶幸的是,我們例子中的語法是簡單而完整的。在我們實現我們的語法之前,我們需要詳細的講解一下我們的設計。

4.1、設計AST(抽象語法樹)

語法分析的最終結果是抽象語法樹AST,正如我們將看到的,Bison生成抽象語法樹的最優工具;我們唯一需要做的事情就是將我們的代碼插入到語法中去。

文本形式字符串,例如”int x”代表了我們語言的文本形式,和這個類似,抽象語法樹AST則代表了我們語言在內存中的表現形式一樣(在語言在組裝成而進程碼之前)。正因如此,我們要 在把這些插入在語法分析中的數據結構首先設計好。這個過程是非常直接的,因為我們為語法中的每個語義單元創建了一個結構。方法聲明、方法調用,變量聲明, 引用,這些都構成了抽象語法樹的節點。我們語言的抽象語法樹的節點如下圖:
Our toy language ast
上圖的C++代碼如下:
node.h文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#include <iostream>
#include <vector>
#include <llvm/Value.h>
 
class CodeGenContext;
class NStatement;
class NExpression;
class NVariableDeclaration;
 
typedef std::vector<NStatement*> StatementList;
typedef std::vector<NExpression*> ExpressionList;
typedef std::vector<NVariableDeclaration*> VariableList;
 
class Node {
public:
    virtual ~Node() {}
    virtual llvm::Value* codeGen(CodeGenContext& context) { }
};
 
class NExpression : public Node {
};
 
class NStatement : public Node {
};
 
class NInteger : public NExpression {
public:
    long long value;
    NInteger(long long value) : value(value) { }
    virtual llvm::Value* codeGen(CodeGenContext& context);
};
 
class NDouble : public NExpression {
public:
    double value;
    NDouble(double value) : value(value) { }
    virtual llvm::Value* codeGen(CodeGenContext& context);
};
 
class NIdentifier : public NExpression {
public:
    std::string name;
    NIdentifier(const std::string& name) : name(name) { }
    virtual llvm::Value* codeGen(CodeGenContext& context);
};
 
class NMethodCall : public NExpression {
public:
    const NIdentifier& id;
    ExpressionList arguments;
    NMethodCall(const NIdentifier& id, ExpressionList& arguments) :
        id(id), arguments(arguments) { }
    NMethodCall(const NIdentifier& id) : id(id) { }
    virtual llvm::Value* codeGen(CodeGenContext& context);
};
 
class NBinaryOperator : public NExpression {
public:
    int op;
    NExpression& lhs;
    NExpression& rhs;
    NBinaryOperator(NExpression& lhs, int op, NExpression& rhs) :
        lhs(lhs), rhs(rhs), op(op) { }
    virtual llvm::Value* codeGen(CodeGenContext& context);
};
 
class NAssignment : public NExpression {
public:
    NIdentifier& lhs;
    NExpression& rhs;
    NAssignment(NIdentifier& lhs, NExpression& rhs) :
        lhs(lhs), rhs(rhs) { }
    virtual llvm::Value* codeGen(CodeGenContext& context);
};
 
class NBlock : public NExpression {
public:
    StatementList statements;
    NBlock() { }
    virtual llvm::Value* codeGen(CodeGenContext& context);
};
 
class NExpressionStatement : public NStatement {
public:
    NExpression& expression;
    NExpressionStatement(NExpression& expression) :
        expression(expression) { }
    virtual llvm::Value* codeGen(CodeGenContext& context);
};
 
class NVariableDeclaration : public NStatement {
public:
    const NIdentifier& type;
    NIdentifier& id;
    NExpression *assignmentExpr;
    NVariableDeclaration(const NIdentifier& type, NIdentifier& id) :
        type(type), id(id) { }
    NVariableDeclaration(const NIdentifier& type, NIdentifier& id, NExpression *assignmentExpr) :
        type(type), id(id), assignmentExpr(assignmentExpr) { }
    virtual llvm::Value* codeGen(CodeGenContext& context);
};
 
class NFunctionDeclaration : public NStatement {
public:
    const NIdentifier& type;
    const NIdentifier& id;
    VariableList arguments;
    NBlock& block;
    NFunctionDeclaration(const NIdentifier& type, const NIdentifier& id,
            const VariableList& arguments, NBlock& block) :
        type(type), id(id), arguments(arguments), block(block) { }
    virtual llvm::Value* codeGen(CodeGenContext& context);
};

非常的清晰明了,我們省略了getter和setter方法,這里只列出了共有成員;這些類也不需要影藏私有數據,并省略了codeGen方法。在我們導出AST成LLVM的字節碼時,就需要使用到這個方法。

4.2、Bison介紹

bison的語法定義文件同樣是由這些標記構成的最復雜的部分。這并不是說技術上有多復雜,但是我也會花一些時間來討論一下Bison的語法細節, 好,現在讓我們立刻來熟悉一下Bison的語法。我們將使用基于類似于BNF的語法,使用定義的好終結符和非終結符來組成我們有效的每一個語句和表達式 (這些語句和表達式就代表我們之前定義的AST節點)。例如:

if_stmt : IF '(' condition ')' block { /* do stuff when this rule is encountered */ }
| IF '(' condition ')' { ... }
;

在上面例子中,我們定義了一個if語句(如果我們支持if語句話),它和BNF不同之處在于,每個語法后面都跟了一系列動作(在’{‘和’}'之間 的內容)。這個動作將在此條語法被識別(譯者注:歸約)的時候被執行。這個過程將會遞歸地按從葉子符號到根節點符號的次序執行,在這個過程,每一個非終結 符最終會被合并為一棵大的語法樹。你將會看到的’$$’符號代表著當前樹的跟節點(譯者注:’$$’代表本條語法規則中冒號左邊的部分的語義內容)。此 外’$1′代表了本條規則葉子中的第一個符號(譯者注:’$1′代表了本條語法規則冒號右邊的內容,$1代表冒號右邊的第一個符號的語義值)。在上面的例 子中,當’condition’有效時我們將會把$3 賦值給$$。這個例子可以解釋如何將我們AST和語法規則關聯起來。我們將在每一條規則中通常賦值一個節點到$$,最后這些規則會合并成一個大的抽象語法 樹。Bison的部分是我們語言最復雜的部分,你需要花一點時間去理解它。此外到此為止,你還沒有看到完整的代碼。下面就是完整的Bison部分的代碼:
parser.y

%{
#include "node.h"
NBlock *programBlock; /* the top level root node of our final AST */

extern int yylex();
void yyerror(const char *s) { printf("ERROR: %s\n", s); }
%}

/* Represents the many different ways we can access our data */
%union {
Node *node;
NBlock *block;
NExpression *expr;
NStatement *stmt;
NIdentifier *ident;
NVariableDeclaration *var_decl;
std::vector *varvec;
std::vector *exprvec;
std::string *string;
int token;
}

/* Define our terminal symbols (tokens). This should
match our tokens.l lex file. We also define the node type
they represent.
*/
%token TIDENTIFIER TINTEGER TDOUBLE
%token TCEQ TCNE TCLT TCLE TCGT TCGE TEQUAL
%token TLPAREN TRPAREN TLBRACE TRBRACE TCOMMA TDOT
%token TPLUS TMINUS TMUL TDIV

/* Define the type of node our nonterminal symbols represent.
The types refer to the %union declaration above. Ex: when
we call an ident (defined by union type ident) we are really
calling an (NIdentifier*). It makes the compiler happy.
*/
%type ident
%type numeric expr
%type func_decl_args
%type call_args
%type program stmts block
%type stmt var_decl func_decl
%type comparison

/* Operator precedence for mathematical operators */
%left TPLUS TMINUS
%left TMUL TDIV

%start program

%%

program : stmts { programBlock = $1; }
;

stmts : stmt { $$ = new NBlock(); $$->statements.push_back($1); }
| stmts stmt { $1->statements.push_back($2); }
;

stmt : var_decl | func_decl
| expr { $$ = new NExpressionStatement(*$1); }
;

block : TLBRACE stmts TRBRACE { $$ = $2; }
| TLBRACE TRBRACE { $$ = new NBlock(); }
;

var_decl : ident ident { $$ = new NVariableDeclaration(*$1, *$2); }
| ident ident TEQUAL expr { $$ = new NVariableDeclaration(*$1, *$2, $4); }
;

func_decl : ident ident TLPAREN func_decl_args TRPAREN block
{ $$ = new NFunctionDeclaration(*$1, *$2, *$4, *$6); delete $4; }
;

func_decl_args : /*blank*/ { $$ = new VariableList(); }
| var_decl { $$ = new VariableList(); $$->push_back($1); }
| func_decl_args TCOMMA var_decl { $1->push_back($3); }
;

ident : TIDENTIFIER { $$ = new NIdentifier(*$1); delete $1; }
;

numeric : TINTEGER { $$ = new NInteger(atol($1->c_str())); delete $1; }
| TDOUBLE { $$ = new NDouble(atof($1->c_str())); delete $1; }
;

expr : ident TEQUAL expr { $$ = new NAssignment(*$1, *$3); }
| ident TLPAREN call_args TRPAREN { $$ = new NMethodCall(*$1, *$3); delete $3; }
| ident { $$ = $1; }
| numeric
| expr comparison expr { $$ = new NBinaryOperator(*$1, $2, *$3); }
| TLPAREN expr TRPAREN { $$ = $2; }
;

call_args : /*blank*/ { $$ = new ExpressionList(); }
| expr { $$ = new ExpressionList(); $$->push_back($1); }
| call_args TCOMMA expr { $1->push_back($3); }
;

comparison : TCEQ | TCNE | TCLT | TCLE | TCGT | TCGE
| TPLUS | TMINUS | TMUL | TDIV
;

%%

5、生成Flex和Bison代碼

現在我們有了Flex的token.l文件和Bison的parser.y文件。我們需要將這兩個文件傳遞給工具,并由工具來生成c++代碼文件。 注意Bison同時會為Flex生成parser.hpp頭文件;這樣做是通過-d開關實現的,這個開關是的我們的標記聲明和源文件分開,這樣就是的我們 可以讓這些token標記被其他的程序使用。下面的命令創建parser.cpp,parser.hpp和tokens.cpp源文件。

$ bison -d -o parser.cpp parser.y
$ lex -o tokens.cpp tokens.l

如果上述工作都沒有出錯的話,我們現在位置已經完成了我們編譯器工作總量的2/3。如果你現在想測試一下我們的代碼,你可以編譯一個簡單的main.cpp程序:

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include "node.h"
extern NBlock* programBlock;
extern int yyparse();
 
int main(int argc, char **argv)
{
    yyparse();
    std::cout << programBlock << endl;
    return 0;
}

你可以編譯這些文件:
$ g++ -o parser parser.cpp tokens.cpp main.cpp
現在你需要安裝LLVM了,因為llvm::Value被node.h引用了。如果你不想這么做,只是想測試一下Flex和Bison部分,你可以注釋掉node.h中codeGen()方法。

如果上述工作都完成了,你現在將有一個語法分析器,這個語法分析器將從標準輸入讀入,并打出在內存中代表抽象語法樹跟節點的內存非零地址。

6、組裝AST和LLVM

編譯器的下一步很自然地是應該將AST轉換成機器碼。這意味著將每一個語義節點轉換成等價的機器指令。LLVM將幫助我們把這步變得非常簡單,因為LLVM將真實的指令抽象成類似AST的指令。這意味著我們真正要做的事就是將AST轉換成抽象指令。
你可以想象這個過程是從抽象語法樹的根節點開始遍歷每一個樹上節點并產生字節碼的過程。現在就是使用我們在Node中定義的codeGen方法的時候了。 例如,當我們遍歷NBlock代碼的時候(語義上NBlock代表一組我們語言的語句的集合),我們將調用列表中每條語句的codeGen方法。上面步驟 代碼類似如下的形式:

1
2
3
4
5
6
7
8
9
10
11
Value* NBlock::codeGen(CodeGenContext& context)
{
    StatementList::const_iterator it;
    Value *last = NULL;
    for (it = statements.begin(); it != statements.end(); it++) {
        std::cout << "Generating code for " << typeid(**it).name() << endl;
        last = (**it).codeGen(context);
    }
    std::cout << "Creating block" << endl;
    return last;
}

我們將實現抽象語法樹上所有節點的codeGen方法,然后在向下遍歷樹的時候調用它,并隱式的遍歷我們整個抽象語法樹。在這個過程中,我們在CodeGenContext類來告訴我們生成字節碼的位置。

6.1、關于LLVM要注意的一些信息

LLVM最大的一個確定就是,你很難找到LLVM的相關文檔。在線手冊、教程、或其他的文檔都沒有及時的得到相關維護,這些文檔大部分都是過期的文檔。除非你去深入研究,否則你很難找到關于C++ API的信息。如果你自己安裝LLVM,docs
是最新的文檔。

我發現最好學習LLVM的方法就是通過LLVM的例子去學習。在LLVM的壓縮包的’example’目錄下有很多快速生成字節碼的例子。在LLVM demo site上可以將C做輸入,然后生成C++ API的例子。以這些例子提供的方法,我找到了類似于int x = 5 ;的指令的生成方法。我使用這些工具實現大部分的節點。

關于LLVM的問題描述到此為止,我將在下面羅列出codegen.h和codegen.cpp的源代碼

codegen.h的內容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <stack>
#include <llvm/Module.h>
#include <llvm/Function.h>
#include <llvm/PassManager.h>
#include <llvm/CallingConv.h>
#include <llvm/Bitcode/ReaderWriter.h>
#include <llvm/Analysis/Verifier.h>
#include <llvm/Assembly/PrintModulePass.h>
#include <llvm/Support/IRBuilder.h>
#include <llvm/ModuleProvider.h>
#include <llvm/ExecutionEngine/GenericValue.h>
#include <llvm/ExecutionEngine/JIT.h>
#include <llvm/Support/raw_ostream.h>
 
using namespace llvm;
 
class NBlock;
 
class CodeGenBlock {
public:
    BasicBlock *block;
    std::map<std::string , Value*> locals;
};
 
class CodeGenContext {
    std::stack<codegenblock  *> blocks;
    Function *mainFunction;
 
public:
    Module *module;
    CodeGenContext() { module = new Module("main"); }
 
    void generateCode(NBlock& root);
    GenericValue runCode();
    std::map<std::string , Value*>& locals() { return blocks.top()->locals; }
    BasicBlock *currentBlock() { return blocks.top()->block; }
    void pushBlock(BasicBlock *block) { blocks.push(new CodeGenBlock()); blocks.top()->block = block; }
    void popBlock() { CodeGenBlock *top = blocks.top(); blocks.pop(); delete top; }
};

codegen.cpp的內容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
#include "node.h"
#include "codegen.h"
#include "parser.hpp"
 
using namespace std;
 
/* Compile the AST into a module */
void CodeGenContext::generateCode(NBlock& root)
{
    std::cout << "Generating code...\n";
 
    /* Create the top level interpreter function to call as entry */
    vector<const type*> argTypes;
    FunctionType *ftype = FunctionType::get(Type::VoidTy, argTypes, false);
    mainFunction = Function::Create(ftype, GlobalValue::InternalLinkage, "main", module);
    BasicBlock *bblock = BasicBlock::Create("entry", mainFunction, 0);
 
    /* Push a new variable/block context */
    pushBlock(bblock);
    root.codeGen(*this); /* emit bytecode for the toplevel block */
    ReturnInst::Create(bblock);
    popBlock();
 
    /* Print the bytecode in a human-readable format
       to see if our program compiled properly
     */
    std::cout << "Code is generated.\n";
    PassManager pm;
    pm.add(createPrintModulePass(&outs()));
    pm.run(*module);
}
 
/* Executes the AST by running the main function */
GenericValue CodeGenContext::runCode() {
    std::cout << "Running code...\n";
    ExistingModuleProvider *mp = new ExistingModuleProvider(module);
    ExecutionEngine *ee = ExecutionEngine::create(mp, false);
    vector<genericvalue> noargs;
    GenericValue v = ee->runFunction(mainFunction, noargs);
    std::cout << "Code was run.\n";
    return v;
}
 
/* Returns an LLVM type based on the identifier */
static const Type *typeOf(const NIdentifier& type)
{
    if (type.name.compare("int") == 0) {
        return Type::Int64Ty;
    }
    else if (type.name.compare("double") == 0) {
        return Type::FP128Ty;
    }
    return Type::VoidTy;
}
 
/* -- Code Generation -- */
 
Value* NInteger::codeGen(CodeGenContext& context)
{
    std::cout << "Creating integer: " << value << endl;
    return ConstantInt::get(Type::Int64Ty, value, true);
}
 
Value* NDouble::codeGen(CodeGenContext& context)
{
    std::cout << "Creating double: " << value << endl;
    return ConstantFP::get(Type::FP128Ty, value);
}
 
Value* NIdentifier::codeGen(CodeGenContext& context)
{
    std::cout << "Creating identifier reference: " << name << endl;
    if (context.locals().find(name) == context.locals().end()) {
        std::cerr << "undeclared variable " << name << endl;
        return NULL;
    }
    return new LoadInst(context.locals()[name], "", false, context.currentBlock());
}
 
Value* NMethodCall::codeGen(CodeGenContext& context)
{
    Function *function = context.module->getFunction(id.name.c_str());
    if (function == NULL) {
        std::cerr << "no such function " << id.name << endl;
    }
    std::vector<value *> args;
    ExpressionList::const_iterator it;
    for (it = arguments.begin(); it != arguments.end(); it++) {
        args.push_back((**it).codeGen(context));
    }
    CallInst *call = CallInst::Create(function, args.begin(), args.end(), "", context.currentBlock());
    std::cout << "Creating method call: " << id.name << endl;
    return call;
}
 
Value* NBinaryOperator::codeGen(CodeGenContext& context)
{
    std::cout << "Creating binary operation " << op << endl;
    Instruction::BinaryOps instr;
    switch (op) {
        case TPLUS:     instr = Instruction::Add; goto math;
        case TMINUS:    instr = Instruction::Sub; goto math;
        case TMUL:      instr = Instruction::Mul; goto math;
        case TDIV:      instr = Instruction::SDiv; goto math;
 
        /* TODO comparison */
    }
 
    return NULL;
math:
    return BinaryOperator::Create(instr, lhs.codeGen(context),
        rhs.codeGen(context), "", context.currentBlock());
}
 
Value* NAssignment::codeGen(CodeGenContext& context)
{
    std::cout << "Creating assignment for " << lhs.name << endl;
    if (context.locals().find(lhs.name) == context.locals().end()) {
        std::cerr << "undeclared variable " << lhs.name << endl;
        return NULL;
    }
    return new StoreInst(rhs.codeGen(context), context.locals()[lhs.name], false, context.currentBlock());
}
 
Value* NBlock::codeGen(CodeGenContext& context)
{
    StatementList::const_iterator it;
    Value *last = NULL;
    for (it = statements.begin(); it != statements.end(); it++) {
        std::cout << "Generating code for " << typeid(**it).name() << endl;
        last = (**it).codeGen(context);
    }
    std::cout << "Creating block" << endl;
    return last;
}
 
Value* NExpressionStatement::codeGen(CodeGenContext& context)
{
    std::cout << "Generating code for " << typeid(expression).name() << endl;
    return expression.codeGen(context);
}
 
Value* NVariableDeclaration::codeGen(CodeGenContext& context)
{
    std::cout << "Creating variable declaration " << type.name << " " << id.name << endl;
    AllocaInst *alloc = new AllocaInst(typeOf(type), id.name.c_str(), context.currentBlock());
    context.locals()[id.name] = alloc;
    if (assignmentExpr != NULL) {
        NAssignment assn(id, *assignmentExpr);
        assn.codeGen(context);
    }
    return alloc;
}
 
Value* NFunctionDeclaration::codeGen(CodeGenContext& context)
{
    vector<const type*> argTypes;
    VariableList::const_iterator it;
    for (it = arguments.begin(); it != arguments.end(); it++) {
        argTypes.push_back(typeOf((**it).type));
    }
    FunctionType *ftype = FunctionType::get(typeOf(type), argTypes, false);
    Function *function = Function::Create(ftype, GlobalValue::InternalLinkage, id.name.c_str(), context.module);
    BasicBlock *bblock = BasicBlock::Create("entry", function, 0);
 
    context.pushBlock(bblock);
 
    for (it = arguments.begin(); it != arguments.end(); it++) {
        (**it).codeGen(context);
    }
 
    block.codeGen(context);
    ReturnInst::Create(bblock);
 
    context.popBlock();
    std::cout << "Creating function: " << id.name << endl;
    return function;
}

上述羅列很多的代碼,然而這部份代碼的含義需要你自己去探索。我在這里只會提及一下你需要注意的內容:

  • 我們在CodeGenContext類中使用一個語句塊的棧來保存最后進入的block(因為語句都被增加到blocks中)
  • 我們同樣用個堆棧來保存每組語句塊中的符號表
  • 我們設計的語言只會知道他當前范圍內的內容.要支持“全局”上下的做法,你必須向上搜索整個堆棧中每一個語句塊,知道你最后發現你匹配的符號(現在我們只是簡單地搜索堆棧中最頂層的符號表)。
  • 在我們進入一個語句塊之前,我們需要將語句塊壓棧,離開語句塊時將語句塊出棧

剩下的內容都LLVM相關了,在這個主題上我并不是專家。但是迄今為止,我們已經有了編譯和運行我們語言的所有代碼。

7、編譯和運行我們的語言

7.1、編譯我們的語言

我們已經有了代碼,現在我們怎么運行它?LLVM有著非常復雜的聯接link,幸運的是,如果你是自己安裝的LLVM,那么你就應該有一個llvm-config二進制程序,這個程序返回你需要的所有編譯和聯接選項。
我們也要同時更新我們的main.cpp的內容使之可以編譯和運行我們的代碼,這次我們main.cpp的內容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include "codegen.h"
#include "node.h"
 
using namespace std;
 
extern int yyparse();
extern NBlock* programBlock;
 
int main(int argc, char **argv)
{
    yyparse();
    std::cout << programBlock << endl;
 
    CodeGenContext context;
    context.generateCode(*programBlock);
    context.runCode();
 
    return 0;
}

現在我們需要這樣來編譯這些代碼
$ g++ -o parser `llvm-config –libs core jit native –cxxflags –ldflags` *.cpp
你也可以編寫一個Makefile來進行編譯

all: parser

clean:
rm parser.cpp parser.hpp parser tokens.cpp

parser.cpp: parser.y
bison -d -o $@ $^

parser.hpp: parser.cpp

tokens.cpp: tokens.l parser.hpp
lex -o $@ $^

parser: parser.cpp codegen.cpp main.cpp tokens.cpp
g++ -o $@ `llvm-config --libs core jit native --cxxflags --ldflags` *.cpp

7.2、運行我們的語言

假設上述所有工作都圓滿完成,那么現在你將有一個名為parser的二進制程序。運行它,還記得我們那個典型例子嗎?讓我們看看我們的編譯器工作的如何。

$ echo 'int do_math(int a) { int x = a * 5 + 3 } do_math(10)' | ./parser
0x100a00f10
Generating code...
Generating code for 20NFunctionDeclaration
Creating variable declaration int a
Generating code for 20NVariableDeclaration
Creating variable declaration int x
Creating assignment for x
Creating binary operation 276
Creating binary operation 274
Creating integer: 3
Creating integer: 5
Creating identifier reference: a
Creating block
Creating function: do_math
Generating code for 20NExpressionStatement
Generating code for 11NMethodCall
Creating integer: 10
Creating method call: do_math
Creating block
Code is generated.
; ModuleID = 'main'

define internal void @main() {
entry:
%0 = call i64 @do_math(i64 10) ; [#uses=0]
ret void
}

define internal i64 @do_math(i64) {
entry:
%a = alloca i64 ; [#uses=1]
%x = alloca i64 ; [#uses=1]
%1 = add i64 5, 3 ; [#uses=1]
%2 = load i64* %a ; [#uses=1]
%3 = mul i64 %2, %1 ; [#uses=1]
store i64 %3, i64* %x
ret void
}
Running code...
Code was run.

8、結論

是不是非常的酷?我同意如果你只是從這篇文章中拷貝粘貼的話,你可能會對運行得到的結果感覺有點失望,但是這點結果可能也會激發你更大的興趣。此 外,這就是本文的意義,這不是本篇指導文章的結束,這只是一個開始。因為有了這篇文章的介紹,你可以在文法分析,語法分析和裝配語言的時候附加上一些瘋狂 的特性,然后創造出一個你自己命名的語言。你現在已經可以編譯語句塊了,那么你現在應該已經有如何繼續下去的基本想法。
本文完整的代碼在Github這里。我一直都在避免提到這個代碼,因為這個代碼不是本文的重點,而僅僅是帶過這部分代碼。

我意識到這是一篇非常長的文章,并且這篇文章中難免會有出錯的地方,如果你找到了任何問題,在你覺得有空的時候,歡迎你給我發電子郵件,我將會調整我的文章。你如果向想我們共享一些信息,你也可以在你覺得有空的時候寫信給我們。


轉自:http://coolshell.cn/articles/1547.html

英文原文:http://gnuu.org/2009/09/18/writing-your-own-toy-compiler/


posted on 2011-01-03 22:25 會飛的兔子 閱讀(2312) 評論(1)  編輯 收藏 引用 所屬分類: 編譯原理

Feedback

# re: 使用Flex Bison 和LLVM編寫自己的編譯器 2013-12-23 19:25 程浩
如果可以看看bison源代碼分析,就能很好的理解這篇博文并很好的使用工具了。  回復  更多評論
  

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            久久久精品午夜少妇| 你懂的视频欧美| 每日更新成人在线视频| 亚洲日韩视频| 亚洲九九精品| 亚洲欧洲另类国产综合| 黑人一区二区| 国内成人精品一区| 依依成人综合视频| 亚洲精品中文字幕在线| 亚洲精品一二| 欧美亚洲一区二区在线观看| 午夜精品久久久久99热蜜桃导演| 欧美激情视频一区二区三区在线播放 | 欧美激情国产高清| 亚洲国产mv| 亚洲国产精品悠悠久久琪琪| 午夜激情综合网| 亚洲精品国产精品久久清纯直播 | 美女日韩欧美| 欧美激情一区二区三区在线| 国产精品入口| 国精品一区二区三区| 亚洲黄色一区二区三区| 一本色道久久88亚洲综合88| 久久久久国产精品厨房| 噜噜噜91成人网| 亚洲永久在线| 欧美成人伊人久久综合网| 国产精品久久久久秋霞鲁丝| 亚洲国产精品久久久久秋霞不卡| 亚洲制服欧美中文字幕中文字幕| 久久精品国产69国产精品亚洲 | 欧美日韩高清区| 精品51国产黑色丝袜高跟鞋| 先锋a资源在线看亚洲| 最新日韩中文字幕| 免费人成精品欧美精品| 在线观看日韩专区| 久久综合网hezyo| 亚洲精品久久| 久久超碰97中文字幕| 亚洲在线一区二区| 国产精品视频久久久| 午夜精品成人在线视频| 亚洲午夜在线观看| 国产精品青草综合久久久久99| 正在播放日韩| 亚洲男人的天堂在线观看| 欧美日韩高清在线| 亚洲国产精品福利| 91久久精品国产91久久性色tv| 欧美a级片一区| 亚洲综合日韩| 欧美1区2区| 午夜视频在线观看一区二区| 欧美亚洲午夜视频在线观看| 在线观看亚洲视频| 宅男精品导航| 在线观看日韩精品| 亚洲视频在线观看| 日韩午夜电影| 欧美一级专区| 亚洲欧美日韩精品在线| 麻豆av一区二区三区久久| 亚洲视频在线播放| 久久综合亚州| 欧美一区二区日韩一区二区| 国产亚洲视频在线| 日韩视频第一页| 麻豆av一区二区三区| 欧美一区影院| 国产精品久久久亚洲一区 | 狠狠色噜噜狠狠色综合久| 欧美激情国产日韩精品一区18| 久久综合国产精品| 欧美麻豆久久久久久中文| 久久久亚洲国产美女国产盗摄| 欧美日韩综合网| 西瓜成人精品人成网站| 欧美激情中文字幕乱码免费| 国产精品久久毛片a| 中文国产成人精品久久一| 99热精品在线观看| 欧美激情一区二区三区全黄| 欧美精品久久久久久久久久| 久热精品视频在线观看| 国产亚洲精品久久飘花| 亚洲黄一区二区三区| 久久久久女教师免费一区| 久久久无码精品亚洲日韩按摩| 久久天天躁狠狠躁夜夜爽蜜月| 欧美精品免费在线观看| 久久精品国亚洲| 亚洲第一精品夜夜躁人人躁| 蜜臀久久99精品久久久画质超高清 | 日韩亚洲一区在线播放| 欧美日韩福利| 久久久久成人精品| 最新成人在线| 久久综合久久久久88| 影音先锋中文字幕一区| 欧美久久九九| 日韩视频在线播放| 国产伦精品免费视频| 美日韩精品免费| 日韩天堂av| 在线日韩av| 激情久久影院| 国产三区精品| 国产欧美日韩免费| 欧美亚洲成人精品| 欧美精品videossex性护士| 欧美一区二区三区在线看| 亚洲视频1区| 久久久久一区| 一区二区三区久久精品| 午夜精品免费在线| 亚洲精品小视频| 亚洲国内高清视频| 在线观看中文字幕不卡| 精久久久久久久久久久| 伊人春色精品| 一区电影在线观看| 午夜欧美大尺度福利影院在线看| 亚洲伦理网站| 亚洲永久免费精品| 久久美女艺术照精彩视频福利播放| 牛牛影视久久网| 久久久久亚洲综合| 久久国产日韩欧美| 亚洲精品小视频在线观看| 亚洲日本中文| 99re热这里只有精品免费视频| 亚洲精品一区二区在线| 午夜日韩在线观看| 欧美国产大片| 含羞草久久爱69一区| 日韩一二在线观看| 久久精品99无色码中文字幕| 亚洲国产经典视频| 亚洲一区中文字幕在线观看| 久久精品主播| 国产精品色婷婷| 99在线精品视频| 欧美xx69| 久久久久久91香蕉国产| 欧美高清视频| 久久久www| 亚洲欧美视频在线观看| 最新亚洲激情| 久久成人免费日本黄色| 国产精品高潮在线| 亚洲午夜女主播在线直播| 亚洲欧洲精品一区二区三区| 久久riav二区三区| 国产精品一区免费在线观看| 日韩视频―中文字幕| 日韩视频一区二区在线观看| 久久在线精品| 国产精品99久久不卡二区| 日韩视频专区| 国产欧美日韩中文字幕在线| 亚洲欧美视频一区二区三区| 亚洲欧美日韩一区二区在线 | 国产欧美短视频| 欧美亚洲视频| 欧美在线播放视频| 亚洲成色777777女色窝| 久久久久久69| 亚洲小说春色综合另类电影| 国产偷国产偷亚洲高清97cao| 久久久人成影片一区二区三区观看 | 国产精品免费在线| 久久精品欧美日韩精品| 欧美成人有码| 久久久精品国产一区二区三区 | 久久精品视频播放| 日韩视频精品在线观看| 亚洲最新合集| 99国产一区| 亚洲视频一区二区在线观看| 欧美一级片久久久久久久| 日韩视频一区二区三区在线播放免费观看 | 亚洲精品国产精品国产自| 国产精品入口夜色视频大尺度 | 欧美亚洲一区| 欧美日韩国产一区二区| 欧美国产大片| 亚洲第一狼人社区| 香蕉成人久久| 欧美在线亚洲一区| 国产精品久久久久久亚洲调教| 一区二区欧美国产| 一区二区三区你懂的| 久久xxxx| 欧美日本簧片| 99国产精品国产精品毛片| 久久经典综合| 亚洲永久免费观看|