Compiler Construction Lecture 10/26

Compiler Construction Lecture 10/26

先週の復習

先週はさまざまなCPUのレジスタアーキテクチャ(Register architecture) について学んだ。

例えば Motolora MC6809ならば、


このようなレジスタを持っている。



エミュレータ(Emulator)の構造

ここで勉強するコンパイラは Micro-C と呼ばれるもので、Cを読み 込んで、MC6809のアセンブラ(assembler)を出力する。最近のコン パイラは機械語を直接生成せずにアセンブラを出力するものが多い。 これは、少しでもコンパイラの機種依存成を小さくするためだと考 えられる。



これをIBMPC上で動作させるためにはMC6809のエミュレータを使う。エミュレータ は、もっとも簡単なインタプリタということもできる。ここで使うエミュレータの 構造を見て見よう。

6809 Simulator V09,
   By L.C. Benschop, Eidnhoven The Netherlands.
/usr/open/lectures/kono/compiler/util09/v09.c
は、非常に簡単な構造をしている。

このエミュレータでは、

/* 6809 registers */
Byte ccreg,dpreg;
Word xreg,yreg,ureg,sreg,ureg,pcreg;
Byte d_reg[2];
Word *dreg=(Word *)d_reg;
/* 6809 memory space */
static Byte mem[65536];
という形で、MC6809のレジスタをCのプログラムとして持っている。 ここでAレジスタ,Bレジスタを合わせて16bitのDとして使うことができる。 この部分に ENDIANに依存したプログラムを使っている。この方が速いと いっているが...
#ifdef BIG_ENDIAN /* This is a dirty aliasing trick, but fast! */
 Byte *areg=d_reg;
 Byte *breg=d_reg+1;
#else  
 Byte *breg=d_reg;
 Byte *areg=d_reg+1;
#endif

main()では、以下のようにMC6809の命令を実行(execute)する。

 for(;;){
  ireg=mem[pcreg++];           /*  Fetech 1 instruction */
  (*instrtable[ireg])();       /*  call a procedure for the instruction */
 } 
(実際のプログラムではtraceなどが入っているので、もっと複雑) instrtable には、Cのfunctionが一覧表になっている。
int (*instrtable[])() = {
 neg , ill , ill , com , lsr , ill , ror , asr ,
 asl , rol , dec , ill , inc , tst , jmp , clr ,
 flag0 , flag1 , nop , sync , ill , ill , lbra , lbsr ,
 ill , daa , orcc , ill , andcc , sex , exg , tfr ,
 bra , brn , bhi , bls , bcc , bcs , bne , beq ,
 bvc , bvs , bpl , bmi , bge , blt , bgt , ble ,
 leax , leay , leas , leau , pshs , puls , pshu , pulu ,
 ill , rts , abx , rti , cwai , mul , ill , swi ,
 neg , ill , ill , com , lsr , ill , ror , asr ,
これは表の一部である。同じ命令(instruction)があるのは、たま たま同じcodeが同じ命令だったり、その関数の中でさらに細かく処理が 分かれたりする。

例えば、neg は、2の補数(2's complement)として 符号を変える演算だが、(nega だったら a = -a )

neg()
{
 Byte *ea;
 Word a,r;
 a=0;
 ea=eaddr0();
 a=*ea;
 r=-a;
 SETSTATUS(0,a,r)
 *ea=r;
}
となっている。ここで、eaddr0() は、オペランド(operand)を計算する 関数(function)である。これは、
Byte * eaddr0() /* effective address for NEG..JMP as byte poitner */
{
 switch( (ireg & 0x70) >> 4)
 {
  case 0: return mem+zeropage();
  case 1:case 2:case 3: return 0; /*canthappen*/
  case 4: return areg;
  case 5: return breg;
  case 6: return mem+postbyte();
  case 7: return mem+direct(); 
 }
} 
などとなっている。MC6809には豊富なアドレッシングモード(addressing mode) があり、zeropage, postbye, direct などがそれを示している。

例えば、

     NEGA        レジスタAのnegativeを取る  (inherent/areg,breg)
     NEG  <$80    Direct page $80のnegative を取る (zeropage)
     NEG  $8000   Meomry $8000 のnegative を取る (direct)
     NEG  ,X      Index register X の指すmemoryのnegativeを取る (index/postbyte)
,X は 0,X の略で、offset 0 のindex ということ。 Indexにはpostbyteで指定されるのだが、さらにいろいろなものを使うことが できる。
     NEG  5,X      X + 5 が指すmemoryのnegativeを取る
     NEG  ,X+      X が指すmemoryのnegativeを取る、そして X を一つ進める
     NEG  ,-X      Xを一つ減らしてkら、X が指すmemoryのnegativeを取る
     NEG  A,X      X + A が指すmemoryのnegativeを取る
     NEG  D,X      X + D が指すmemoryのnegativeを取る
     NEG  [5,X]    X + 5 が指すmemoryの中身が指すmemory のnegativeを取る
postbyteの処理は少し複雑なので、ここでははぶこう。良く使う ,X や、5,X などは短くcodingされ、1000,Xなどは長くcodingされるように工夫されている。



問題1

MC6809はbig-endianであり、メモリにはメモリのアドレス が小さい方に、 数値の大きい桁から格納される。また、16bit Dレジスタは、 高い方の桁が8bit A レジスタ、低い方の桁が8bit B レジスタとなっている。 以下の数値は16進表記とする。

メモリの内容が以下のようになっているとき、

0100:   23
0101:   45
0102:   56
0103:   78
0104:   90
0105:   12
5678:   01
5679:   02
567a:   03
567b:   04
7856:   05
7857:   06
7858:   07
7859:   08



アセンブラ(Assembler)を使った計算

MC6809では、基本的に演算は、register to memory で行われる。 ここでは、以下のプログラムを使おう。

int a=4,b=3;
main()
{
    a = a+1-(b-123);
    return a;
}
これを /usr/open/lectures/kono/compiler/mc/mc でコンパイルしてみると、 c.out というファイルに以下の内容がかかれる。(コメントはでないが...)
a       EQU     0
_1
        LDD     #4
        STD     0,Y
        LBRA    _2
b       EQU     2
_2
        LDD     #3
        STD     2,Y
        LBRA    _3
main
        PSHS    U         # function call
        LEAU    ,S        # local variable の場所を取る
        LDD     2,Y       # b を D に入れる
        SUBD    #123      # それから 123 を引く(sub)
        PSHS    D         # D をstackに積む
        LDD     0,Y       # a を D に入れる
        ADDD    #1        # それに 1 を足す(add)
        SUBD    ,S++      # それからstackにつんだものを引く
        STD     0,Y       # それをaに入れる
        LDD     0,Y       # a の値を main() の値として返す。
        PULS    U,PC
_1      RTS
_INITIALIZE     EQU     _1
_GLOBALS        EQU     4
        END
となる。Sがstackであり、Yが大域変数を指し示している。(U は、局所変数 である)

これを走らせるには、若干の準備が必要である。例えばYやSの初期設定 がそれにあたる。また、実際のCでは、引き数の処理なども必要になる。 これらは、Unixではcrt0.o という部分で処理される。Micro-Cのために 簡単なcrt0 driverを作ろう。

        ldy     #$8000          大域変数の設定
        lds     #$FF00          スタックの設定
        ldu     #$FF00          フレームポインタの設定
        lbsr    _INITIALIZE     大域変数の初期化
        lbsr    main            main の呼び出し
        sync                    エミュレータを止める
        INCLUDE c.out           コンパイラの出力をここにもってくる
実際には、これをアセンブルすれば良い。
/usr/open/lectures/kono/compiler/mc/mc test.c
test.c:

Compiled 6 lines.
Total internal labels   : 3.
Total global variables : 4 bytes.
cp /usr/open/lectures/kono/compiler/util09/crt0.asm .
% /usr/open/lectures/kono/compiler/util09/a09 -l test.lst crt0
crt0      crt0.asm  
% /usr/open/lectures/kono/compiler/util09/a09 -l crt0.lst crt0.asm 
いちいちfull pathを入れるのは、面倒なので、symbolic linkするか、 path を張るか、どっちかにしよう。アセンブラの -l crt0.lst オプションは どのような機械語が出力されるかを見るためのものである。



宿題1

mc と v09 を使って、コンパイルとアセンブルを通し、 上のプログラムをtraceして見よ。

上のプログラムをIntel CPU上で、gcc -S test.c を使ってアセンブラを出力し、 上と同様のコメントを付けよ。-O を付けると最適化がかかる。最適化した 場合としない場合で、どのような違いが出るか? (ヒント diff を使うと 簡単...)

来週、11/2 はお休みにします。3連休になりますね。



Kono's home page http://bw-www.ie.u-ryukyu.ac.jp/~kono/