先週の復習
CPUのprogramming modelは、以下の要素からなる。
すべてのCPUに詳しくなる必要はないが、どれか一つの専門家にはなって
いたい所である。
どちらが優れているかという議論はあったが、現在では卵のとがっている
方から割るか、丸い方から割るかぐらいの違いしかないと認識されている
ようだ。
これらの問題は、普通のプログラミングでは考える必要はないが、
効率の良いMachine Languageを生成する場合には考慮する必要が
ある。
現在のプログラミングでは、配列(array)やリスト(list)、構造体(structure) のアクセスが重要なので、Addressing mode は、それを1命令で実現しやすい ように設計されているのが普通だ。だいたい図のようなAddressing mode を採用しているものが多い。
Addressing modeには以下のようなものがある。
Immediate LDA #$F0 constantをloadする extended LDA $F000 指定されたaddressからloadする indexed LDA 5,X indexとoffsetで示されたaddress からload する accumulator offset indexed LDA A,X indexed + post increment LDA A,X+ loadしたあと X を一つ増やす indexed + pre decrement LDA A,-X loadする前に X を一つ減らすさらに、これらを一段間接にしたindirect addressing も用意されている。 演算は、基本的にaccumulatorとmemory間で行われる。
8bit CPU(8080)の拡張によってできたCPUなので、32bit modeでも それをかなり引きずっている。命令はMC6809と同じ8bitの可変長命 令である。80386によって、16bit/32bit切替と仮想記憶がサポート され爆発的な成功を納めた。最近のPentinumPro では、内部でRISC 命令に変換してから実行するというようなことをしている。16bit ではregisterの役割が偏っていたが、32bitでは若干対称性が良く なっている。4本のAccumulator、4本のIndex register を持つ。
Addressing modeには以下のようなものがある。
Immediate movb al,$F0 constantをloadする extended movb al,[$F000] 指定されたaddressからloadする indexed movb al,[EAX+5] indexとoffsetで示されたaddress からload する accumulator offset indexed mov [EBX+EAX] accumulator offset indexed mov [EBX+EAX+5]さらに、x86には segement register というのがあり、それによっ てvirtual memory (メモリ空間) を切り替えることができる。しか し、普通はすべて同じメモリ空間が設定されている。他のCPUでも データ(data)とコード(code)は別空間にできるものが多い。
演算はレジスタとレジスタの間で行うことが多い。
RISC CPUの特徴として32bit 固定長命令を採用している。またパイ プライン処理が深いために、分岐命令のかなり前に(3命令程度) そ の分岐の方向が決まるようにプログラムしないと処理速度が落ちて しまう。Subroutine callは、LR (link register) CTR (count register) 経由で行われるので、単純な一命令にはならない。並列実行される 命令が多いのでコンパイラはそれを考慮したコードを生成しなければ ならないなど、コンパイルは難しいがやりがいがあるCPUでもある。
Addressing modeには以下のようなものがある。
D形式 lbz RT, D (RA) indexとoffsetで示されたaddress からload する X形式 lbzx RT, RA,RB RA+RBで示されたaddress からload する D形式 with update lbzu RT, D (RA) RAがRA+Dになる点が違う X形式 with update lbzux RT, RA,RB RAがRA+RBになる点が違うImmediate addressing はなく、足し算命令を代わりに使う。これにより命令数 を減らしているわけである。さらにEndianを変換しながらload/storeする命令も ある。演算は、3引き数であり、r0に3をたしてr5に入れるなどということが できる。
Overrapped Register Windowは subroutine callの度に多数(7つか ら20程度)のregister fileを切り替える。register fileがいっぱ いになると、割り込みがかかりstackに書き出す。この処理が重い ので、compilerはregister file の切替をできるだけ遅らせなくて はならない。これは、register windowの狙いからすると本末転倒 である。save, restore 命令によりregister windowを切り替える ことになっている。
Delayed branch は、PowerPCと同様にbranchの際のパイプラインの 乱れを避けるためのもので、branchをすることになって一命令 だけbranchの次の命令を実行する。これを行うかどうかを指定する bit が opcode の中に存在する。しかし、一命令だけでパイプラインが 乱れないというわけでもなく、compiler が面倒をみるということならば、 PowerPCと同様のことをしなければならない。これも失敗した技術 だということができるだろう。
Addressing modeには以下のようなものがある。
%g0 %g0 は0に固定されている register addressing ld 3,%r0 indexとoffsetで示されたaddress からload する register addressing ld %r5,%r0Immediate addressing はなく、PowerPCと同様である。set, sethi という 疑似命令が用意されている。これはorで実現されている。演算はPowerPCと 同様3引き数である。
非常に簡潔なアーキテクチャであるが、branchの後の一命令を実行する delayed slotをもっている。これは今では既に時代遅れだと言える。 むしろ、MIPSは、大域的なレジスタ割り当てを持つコンパイラ技術が 注目された。32本の汎用レジスタを持つが、半分を大域的な変数に 割り当てることが可能である。
PowerPC は、bw, cw 上でcompile すれば良い。Sparc は、fish1, fish2 上でcompileすれば良い。Intel x86は、hyde でcompileすれ ば良い。MC6809のcompilerとemulatorは、 /usr/open/lectures/kono/compiler/mc に太田昌宏氏によるcompilerと、 /usr/open/lectures/kono/compiler/util09 にBenschopによる6809 emulatorがある。
dbx というdebuggerを使って、そのAssemberの実行を一命令づつ 調べることができる。
int a[10]; main() { return a[4]; }は、cc -O -S test.c によって、(RISCの場合は-Oを付けた方がより 分かりやすいコードがでることが多い)
.main: # 0x00000000 (H.10.NO_SYMBOL) .file "test.c" l r3,T.22.a(RTOC) l r3,16(r3) bcr BO_ALWAYS,CR0_LTという形にcompileされる。(一部だけ抜きだしている。実際には、その他の 部分がたくさんある) これをdbxで実行するには、
% cc -g test.c % dbx a.out (dbx) stop in main [1] stop in main (dbx) run [1] stopped in main at line 5 5 return a[4]; (dbx) stepi stopped in main at 0x1000047c 0x1000047c (main+0x4) 80630010 l r3,0x10(r3) (dbx) registers $r0: 0xdeadbeef $stkp: 0x2ff7f910 $toc: 0x2005285c $r3: 0x20052880 $r4: 0x2ff7f950 $r5: 0x2ff7f958 $r6: 0xdeadbeef $r7: 0x2ff7fffc $r8: 0x00000000 $r9: 0x20052874 $r10: 0xdeadbeef $r11: 0xdeadbeef $r12: 0xdeadbeef $r13: 0xdeadbeef $r14: 0xdeadbeef $r15: 0xdeadbeef $r16: 0xdeadbeef $r17: 0xdeadbeef $r18: 0x20052600 $r19: 0xdeadbeef $r20: 0xdeadbeef $r21: 0xdeadbeef $r22: 0xdeadbeef $r23: 0xdeadbeef $r24: 0xdeadbeef $r25: 0xdeadbeef $r26: 0xdeadbeef $r27: 0xdeadbeef $r28: 0xdeadbeef $r29: 0xdeadbeef $r30: 0xdeadbeef $r31: 0xdeadbeef $iar: 0x1000047c $msr: 0x0002d0b0 $cr: 0x28222880 $link: 0x1000042c $ctr: 0xdeadbeef $xer: 0x20000000 $mq: 0xdeadbeef $tid: 0x00000000 Condition status = 0:e 1:l 2:e 3:e 4:e 5:l 6:l [unset $noflregs to view floating point registers] in main at 0x1000047c 0x1000047c (main+0x4) 80630010 l r3,0x10(r3) (dbx) qとすれば良い。まず、cc でコンパイルして、dbx をa.outに対して起動している。 main に break point を置いて実行したのち、1命令づつ実行する stepi を 行っている。registerの内容は、registers command で確認できる。
int check = 0x12345678; main() { char i, *ptr; ptr = (char *)✓ i = ptr[1]; return i; }このprogramをcompileしたassemblerを、どれか一つのCPUで表示 させて見よ。また、dbx で i にどのような値が入るかをstepi で確認せよ。そのCPUは、Little-Endian か Big-Endian かを答えよ。 また、 trace の結果を、
Subject: compiler report 10/16というSubjectのメールにして、kono@ie.u-ryukyu.ac.jp まで送ること。 また授業を受けなかったものは、課題を、
Subject: compiler lecture 10/16というサブジェクトで送ること。