随想録(オープンソースコンパイラ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コンパイル
  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 (  )