先週はさまざまなCPUのレジスタアーキテクチャ(Register architecture) について学んだ。
例えば Motolora MC6809ならば、
ここで勉強するコンパイラは 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されるように工夫されている。
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
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 オプションは どのような機械語が出力されるかを見るためのものである。
mc と v09 を使って、コンパイルとアセンブルを通し、 上のプログラムをtraceして見よ。
上のプログラムをIntel CPU上で、gcc -S test.c を使ってアセンブラを出力し、 上と同様のコメントを付けよ。-O を付けると最適化がかかる。最適化した 場合としない場合で、どのような違いが出るか? (ヒント diff を使うと 簡単...)
来週、11/2 はお休みにします。3連休になりますね。