命令は、演算であれば、加算か論理演算かなどの種別を指定する必要があるし、メモリの読み書きや演算結果の正負の判別など、どのような動作を行うべきかを指定する必要がある。この部分をオペレーションコード、略してOPコードと呼ぶ。
そして、それらの動作を行う入力オペランドとしてどれを使い、演算結果(リザルト)をどこに格納するかを指定できなければならない。また、以下のアドレッシングの説明では、特に区別を必要としない場合には、入力オペランドとリザルトを総称してオペランドと呼ぶ。
例えば、加算であれば、入力オペランドが2つとリザルトが1つ必要であり、各命令で、これらの3つのオペランドを独立に指定できるようにするのが、3アドレス命令という形式である。
1~3アドレス命令フォーマット。オペランドやリザルトのアドレスの指定フィールドの個数が1個、2個、3個という命令の作り方がある。 |
前に述べた簡単なコンピュータの場合は、オペランドAだけを指定し、もう一方のオペランドはアキュムレータ(Accと表記)に決め打ちし、リザルトもアキュムレータに格納するという1アドレス命令形式を使っている。この1アドレス形式は、ハードウェア構造が簡単であるというメリットがあるが、前項の説明に見られるように、一つのアキュムレータを使いまわすために、本来は必要のないアキュムレータの退避、復元が頻繁に発生するという問題がある。
2アドレス形式は、1アドレス形式と同様に一方のオペランドとリザルトの格納アドレスは兼用であるが、決め打ちのAccではなく、任意のアドレスCが指定でき、1アドレスに比べると、大幅に自由度が向上している。
Intel/AMDのx86アーキテクチャは、この2アドレス形式の命令アーキテクチャである。一方、PowerPC、SPARCなどのRISC系のアーキテクチャでは3アドレス形式の命令を使うのが一般的である。
付け加えると、0アドレス形式という命令形式もある。オペランドを指定しないで、どうやって計算をするのかと思われるかも知れないが、0アドレス形式では、2個のオペランドの位置もリザルトを格納する位置も決め打ちであり、次の図のようにスタックを使って処理を行う。スタックは、次々とデータをプッシュすると以前に入れたデータの上に新しく入れたデータが積み重なり、一番上のデータを取り出す(ポップする)と、その下のデータが一番上に出てくるという構造である。
0アドレス形式命令による加算の例 |
演算に当たっては、スタックを2回ポップしてスタックのTopとTop-1を取り出し、Top-1を第一入力オペランド、Topを第二入力オペランドとして使用する。そして、演算を行った結果をスタックにプッシュするという動作を行う。従って、演算の前後でみると、スタックの内容は、この図の左から右のよう変化する。
そして、ロード命令の場合は、Topがアクセスするメモリアドレスを格納しており、これをポップしてメモリをアクセスし、ロードされたデータをスタックにプッシュする。また、ストア命令ではTopがメモリアドレス、Top-1がストアすべきデータというように、全ての操作でオペランドとリザルトの位置は決め打ちとなっているので、命令でアドレスを指定する必要はない。
コンパイラは数式C*(A+B)を逆ポーランド記法でA,B,+,C,*のように変換するが、これはAプッシュ、Bプッシュ、加算、Cプッシュ、乗算という操作に1対1に対応するので、計算と相性が良いということで、0アドレスのスタック方式のコンピュータも開発されたが、現状では、メジャーなコンピュータアーキテクチャとしては生き残っていない。
オペランドのアドレス指定であるが、一番単純なのは、レジスタ群の中のレジスタ番号を指定するもので、RISCアーキテクチャではこの形式の指定となっている。しかし、この形式だけでは、メモリのアクセスができない。このため、RISCでは、ロード命令とストア命令だけをメモリをアクセスする命令として定義する。例えば、ロード命令では、オペランドAとして指定したレジスタの内容をアドレスとしてメモリをアクセスし、データをリザルトとして指定したレジスタに格納する。一方、ストア命令では、オペランドAがメモリアドレス、オペランドCが格納すべきデータを保持しているとしてメモリをアクセス行う。
これに対して、CISCアーキテクチャの場合は、演算命令も含めた一般命令で、オペランドとしてレジスタだけでなくメモリを指定することができるようになっているのが一般的である。例えばx86アーキテクチャでは、オペランドAはレジスタ、オペランドB兼リザルトCとしてメモリ、あるいはオペランドAとしてメモリ、オペランドB兼リザルトCとしてレジスタを指定するという命令形式が使用できる。従って、CISCのx86の場合は、メモリに置かれた変数に値を足しこむという操作が1命令で実現できるが、RISCの場合には、ロード命令でメモリから変数をレジスタにロードし、ADD命令で加算し、加算結果をストア命令でメモリに書き戻すという3命令が必要となる。ということで、ある操作に必要な命令数という観点では、CISCはRISCに比べて少なくて済む場合が多い。