随想録(オープンソースコンパイラucc)
19378 ワード
【声明:著作権所有、転載歓迎、商業用途に使用しないでください.連絡ポスト:[email protected]】
オープンソースコンパイラといえば、一般的にはgccが考えられます.しかし、現在gccのコード量が大きすぎて、勉強には向いていません.コード量は学習に適したコンパイラで、googleを押すとlccとuccの2つの項目が基本的に残っています.このうちlccは複数のcpuをサポートしているが、uccは現在x 86のみをサポートしており、長い間メンテナンスされていない.しかし、コードの読み取り可能性から言えばuccを読むことをお勧めします.uccコード量は一般的で、統計してみると、約15000行程度で、構造もはっきりしています.コンパイラはもちろんlex&bisonで生成できますが、uccの純粋な手作りソフトウェアを見てみると、良い学習体験でもあります.
注(もう一つの簡潔なコンパイラはluaとあまり差がありません):
https://github.com/rswier/c4/blob/master/c4.c
これは1つの簡単なcコンパイラで、完成した文法分析、文法分析、コードコンパイル、コードはすべての動作を実行して、肝心なのは全体のファイルも527行です
コンパイルするときは32ビットコード、すなわちgcc-m 32 c 4にコンパイルしてください.c -o c4
1、コードはgithubから読むかダウンロードできる
https://github.com/sheisc/ucc162.3/tree/master/ucc
uccをコンパイルする方法は、最上位ディレクトリにmakeを入力することです.
./ucl --dump-ast --dump-IR hello.c,このときhelloが生成される.s、hello.ast、hello.uil
その中でhello.sはアセンブリファイル、hello.astは生成された構文ツリー、helloを表す.uilは生成された中間コードファイルを表す
a、iterateがあると仮定します.cのファイルです.では、./ucl --dump-ast --dump-IR iterate.cコンパイル
b,生成文法ツリーはiterate.ast,
c、中間コードファイルはiterate.uil,
d、もちろん、最後のiterateが欠かせない.sアセンブリファイル、
2、書籍と結びつけてuccを学ぶ
ネット上あるいは電子商取引のウェブサイトの上で1冊の本があって、鄒昌偉先生が書いた《cコンパイラの剖析》です.この本はuccコンパイラについて話しています.もし皆さんがこの本を見つけることができたら、この本と一緒に勉強することができます.
3、uccは実はツールチェーンです
ucc自体は実はツールの組み合わせです.コンパイラのucl部分は、実際にはcをアセンブリファイルにコンパイルするだけです.obj,objにアセンブリして実行ファイルにリンクし,この部分はas,gccによって完成する.これはgccと同じです.皆さんgcc-v hello.cコンパイルしたら、全部分かりました.
4、uccの入口
5、uclの入り口
6、uclの基本論理コード
上
このコードはuccの最も重要な部分だと思います.ここでParseTranslationUnitは生成語法ツリーを担当し、CheckTranslationUnitは意味分析を担当し、Translateは中間コード生成を担当し、EmitTranslationUnitは中間コードからアセンブリコードへのマッピング部分を完了する.構文ツリーを表示する必要がある場合は、DumpASTを開くことができます.同様に、中間コードを表示する必要がある場合は、DumpIRを開くことができます.工事入口ファイルはuclです.c.文法分析のファイルはlexです.c、input.c,構文解析のファイルはdeclを含む.c、stmt.c、expr.c,意味分析のファイルはdeclchk.c、stmtchk.c、exprchk.c,中間文のファイルはtranexpr.c、transtmt.c、simp.c、gen.c、最後にアセンブリマッピングされたファイルはemitである.c、x86.c、x86linux.c.他のファイルはすべて補助ファイルなので、この点はまだはっきりしています.
7、自己コンパイル
uclの面白いところはセルフコンパイルです.例えばucl自体はgccコンパイルによって生成される.uclファイルが生成されると、uclで元のソースコードをコンパイルし、新しいuclを生成し続けることができます.これが自挙というものだ.
8、その他
uclは上から下への方法で解析される.一般に,この方法は底から上へ行くよりも効率が高い.下から上へのソースコード解析を行うと,移動や規約の操作が止まらないことが想像できる.もちろん規約を行うなら、文法パターン全体にもアクセスしなければなりません.コンパイラの勉強は、パターンという部分に集中することができます.パターンを理解すれば、コンパイラは半分理解します.後続の中間コード生成,peephole最適化,アセンブリマッピングについては,それほど難しくない.もちろん解釈器を設計したいなら、文法ツリーのほうで終わります.
ps:
bnf of c,
オープンソースコンパイラといえば、一般的にはgccが考えられます.しかし、現在gccのコード量が大きすぎて、勉強には向いていません.コード量は学習に適したコンパイラで、googleを押すとlccとuccの2つの項目が基本的に残っています.このうちlccは複数のcpuをサポートしているが、uccは現在x 86のみをサポートしており、長い間メンテナンスされていない.しかし、コードの読み取り可能性から言えばuccを読むことをお勧めします.uccコード量は一般的で、統計してみると、約15000行程度で、構造もはっきりしています.コンパイラはもちろんlex&bisonで生成できますが、uccの純粋な手作りソフトウェアを見てみると、良い学習体験でもあります.
注(もう一つの簡潔なコンパイラはluaとあまり差がありません):
https://github.com/rswier/c4/blob/master/c4.c
これは1つの簡単なcコンパイラで、完成した文法分析、文法分析、コードコンパイル、コードはすべての動作を実行して、肝心なのは全体のファイルも527行です
コンパイルするときは32ビットコード、すなわちgcc-m 32 c 4にコンパイルしてください.c -o c4
1、コードはgithubから読むかダウンロードできる
https://github.com/sheisc/ucc162.3/tree/master/ucc
uccをコンパイルする方法は、最上位ディレクトリにmakeを入力することです.
./ucl --dump-ast --dump-IR hello.c,このときhelloが生成される.s、hello.ast、hello.uil
その中でhello.sはアセンブリファイル、hello.astは生成された構文ツリー、helloを表す.uilは生成された中間コードファイルを表す
a、iterateがあると仮定します.cのファイルです.では、./ucl --dump-ast --dump-IR iterate.cコンパイル
1 int
2 iterate(int data){
3
4 if(1 == data)
5 return 1;
6 else
7 return iterate(data-1) + data;
8 }
b,生成文法ツリーはiterate.ast,
1 function iterate
2 {
3 (if (== 1
4 data)
5 (then
6 (ret 1)
7 end-then)
8 (else
9 (ret (+ (call iterate
10 (- data
11 1))
12 data))
13 end-else)
14 end-if)
15 }
16
~
c、中間コードファイルはiterate.uil,
1 function iterate
2 if (1 != data) goto BB0;
3 return 1;
4 goto BB1;
5 goto BB1;
6 BB0:
7 t0 = data + -1;
8 t1 = iterate(t0);
9 t2 = t1 + data;
10 return t2;
11 BB1:
12 ret
13
14
d、もちろん、最後のiterateが欠かせない.sアセンブリファイル、
1 # Code auto-generated by UCC
2
3 .data
4
5
6
7
8 .text
9
10 .globl iterate
11
12 iterate:
13 pushl %ebp
14 pushl %ebx
15 pushl %esi
16 pushl %edi
17 movl %esp, %ebp
18 subl $12, %esp
19 movl $1, %eax
20 cmpl 20(%ebp), %eax
21 jne .BB0
22 movl $1, %eax
23 jmp .BB1
24 jmp .BB1
25 .BB0:
26 movl 20(%ebp), %eax
27 addl $-1, %eax
28 pushl %eax
29 call iterate
30 addl $4, %esp
31 addl 20(%ebp), %eax
32 .BB1:
33 movl %ebp, %esp
34 popl %edi
35 popl %esi
36 popl %ebx
37 popl %ebp
38 ret
39
2、書籍と結びつけてuccを学ぶ
ネット上あるいは電子商取引のウェブサイトの上で1冊の本があって、鄒昌偉先生が書いた《cコンパイラの剖析》です.この本はuccコンパイラについて話しています.もし皆さんがこの本を見つけることができたら、この本と一緒に勉強することができます.
3、uccは実はツールチェーンです
ucc自体は実はツールの組み合わせです.コンパイラのucl部分は、実際にはcをアセンブリファイルにコンパイルするだけです.obj,objにアセンブリして実行ファイルにリンクし,この部分はas,gccによって完成する.これはgccと同じです.皆さんgcc-v hello.cコンパイルしたら、全部分かりました.
4、uccの入口
int main(int argc, char *argv[])
{
int i;
if (argc <= 1)
{
ShowHelp();
exit(0);
}
Option.oftype = EXE_FILE;
SetupToolChain();
Command = Alloc((argc + 60) * sizeof(char *));
Command[0] = NULL;
i = ParseCmdLine(--argc, ++argv);
for (; i < argc; ++i)
{
if (argv[i][0] == '-')
{
Option.linput = ListAppend(Option.linput, argv[i]);
}
else
{
AddFile(argv[i]);
}
}
for (i = PP_FILE; i <= Option.oftype; ++i)
{
if (InvokeProgram(i) != 0)
{
RemoveFiles();
fprintf(stderr, "ucc invoke command error:");
PrintCommand();
return -1;
}
}
RemoveFiles();
return 0;
}
5、uclの入り口
/**
* The compiler's main entry point.
* The compiler handles C files one by one.
*/
int main(int argc, char *argv[])
{
int i;
CurrentHeap = &ProgramHeap;
argc--; argv++;
i = ParseCommandLine(argc, argv);
SetupRegisters();
SetupLexer();
SetupTypeSystem();
for (; i < argc; ++i)
{
Compile(argv[i]);
}
return (ErrorCount != 0);
}
6、uclの基本論理コード
static void Compile(char *file)
{
AstTranslationUnit transUnit;
Initialize();
// parse preprocessed C file, generate an abstract syntax tree
transUnit = ParseTranslationUnit(file);
// perform semantic check on abstract synatx tree
CheckTranslationUnit(transUnit);
if (ErrorCount != 0)
goto exit;
if (DumpAST)
{
DumpTranslationUnit(transUnit);
}
// translate the abstract synatx tree into intermediate code
Translate(transUnit);
if (DumpIR)
{
DAssemTranslationUnit(transUnit);
}
// emit assembly code from intermediate code
EmitTranslationUnit(transUnit);
exit:
Finalize();
}
上
このコードはuccの最も重要な部分だと思います.ここでParseTranslationUnitは生成語法ツリーを担当し、CheckTranslationUnitは意味分析を担当し、Translateは中間コード生成を担当し、EmitTranslationUnitは中間コードからアセンブリコードへのマッピング部分を完了する.構文ツリーを表示する必要がある場合は、DumpASTを開くことができます.同様に、中間コードを表示する必要がある場合は、DumpIRを開くことができます.工事入口ファイルはuclです.c.文法分析のファイルはlexです.c、input.c,構文解析のファイルはdeclを含む.c、stmt.c、expr.c,意味分析のファイルはdeclchk.c、stmtchk.c、exprchk.c,中間文のファイルはtranexpr.c、transtmt.c、simp.c、gen.c、最後にアセンブリマッピングされたファイルはemitである.c、x86.c、x86linux.c.他のファイルはすべて補助ファイルなので、この点はまだはっきりしています.
7、自己コンパイル
uclの面白いところはセルフコンパイルです.例えばucl自体はgccコンパイルによって生成される.uclファイルが生成されると、uclで元のソースコードをコンパイルし、新しいuclを生成し続けることができます.これが自挙というものだ.
C_SRC = alloc.c ast.c decl.c declchk.c dumpast.c dom.c emit.c \
error.c expr.c exprchk.c flow.c fold.c gen.c \
input.c lex.c output.c reg.c simp.c stmt.c \
stmtchk.c str.c symbol.c tranexpr.c transtmt.c type.c \
ucl.c uildasm.c vector.c x86.c x86linux.c
OBJS = $(C_SRC:.c=.o)
CC = gcc
CFLAGS = -g -D_UCC
UCC = ../driver/ucc
all: $(OBJS) assert.o
$(CC) -o ucl $(CFLAGS) $(OBJS)
clean:
rm -f *.o ucl
test: $(C_SRC)
$(UCC) -o ucl1 $(C_SRC)
mv $(UCCDIR)/ucl $(UCCDIR)/ucl.bak
cp ucl1 $(UCCDIR)/ucl
$(UCC) -o ucl2 $(C_SRC)
mv $(UCCDIR)/ucl.bak $(UCCDIR)/ucl
strip ucl1 ucl2
cmp -l ucl1 ucl2
rm ucl1 ucl2
8、その他
uclは上から下への方法で解析される.一般に,この方法は底から上へ行くよりも効率が高い.下から上へのソースコード解析を行うと,移動や規約の操作が止まらないことが想像できる.もちろん規約を行うなら、文法パターン全体にもアクセスしなければなりません.コンパイラの勉強は、パターンという部分に集中することができます.パターンを理解すれば、コンパイラは半分理解します.後続の中間コード生成,peephole最適化,アセンブリマッピングについては,それほど難しくない.もちろん解釈器を設計したいなら、文法ツリーのほうで終わります.
ps:
bnf of c,
The syntax of C in Backus-Naur Form
::= {}*
::=
|
::= {}* {}*
::=
|
|
::= auto
| register
| static
| extern
| typedef
::= void
| char
| short
| int
| long
| float
| double
| signed
| unsigned
|
|
|
::= { {}+ }
| { {}+ }
|
::= struct
| union
::= {}*
::=
|
::=
| ,
::=
| :
| :
::= {}?
::= * {}* {}?
::= const
| volatile
::=
| ( )
| [ {}? ]
| ( )
| ( {}* )
::=
::=
| ? :
::=
|
::=
|
::=
| |
::=
| ^
::=
| &
::=
| ==
| !=
::=
| <
| >
| <=
| >=
::=
| <<
| >>
::=
| +
| -
::=
| *
| /
| %
::=
| ( )
::=
| ++
| --
|
| sizeof
| sizeof
::=
| [ ]
| ( {}* )
| .
| ->
| ++
| --
::=
|
|
| ( )
::=
|
|
|
::=
| ,
::=
|
::= =
| *=
| /=
| %=
| +=
| -=
| <<=
| >>=
| &=
| ^=
| |=
::= &
| *
| +
| -
| ~
| !
::= {}+ {}?
::=
| , ...
::=
| ,
::= {}+
| {}+
| {}+
::=
|
|
::= ( )
| {}? [ {}? ]
| {}? ( {|? )
::= enum { }
| enum { }
| enum
::=
| ,
::=
| =
::=
::= {}+ {}* ;
::=
| =
::=
| { }
| { , }
::=
| ,
::= { {}* {}* }
::=
|
|
|
|
|
::= :
| case :
| default :
::= {}? ;
::= if ( )
| if ( )