第6回講義資料 2009/10/23

コンピュータの命令

アーキテクチャの基本となるのは命令セットです。第2、3、4回と何度か命令セットの話は出ていますが、第5回はもうすこし具体的にコンピュータの命令を見ていきます。

アーキテクチャという用語は、IBM System/360から登場し、最初は命令セットによって定義されるコンピュータの機能や構造を意味していましたが、現在は内部構造もアーキテクチャと呼ぶので、従来の命令の構成は命令セットアーキテクチャと(ISA)と呼ぶことが多いです。

コンピュータの命令(機械語命令)
コンピュータの機械語命令は普段プログラミングに用いる高級言語とは異なり、基本的には制御構造などはない。命令を表現するコードと、命令の対象を指定するコードから成り立つ。
  • 命令+アドレシング
  • オペコード+オペランド
コンピュータの命令の対象は、メモリやI/Oである。メモリは実際にはプロセッサ内部のレジスタ、メインメモリがある。I/Oはメモリ空間に位置づけられ、メモリと同じようにアドレシングできるものと、I/O空間は別に用意され、I/O専用命令によって処理される場合がある。

アセンブリ言語
機械語命令は数値にコーディングされたものなので、人間にとって簡単に理解できるものではない。あくまでも実行の効率を考え作られたものである。アセンブリ言語は命令をわかりやすく表現した記述方式で、アセンブラによって機械語に変換する。高級言語とは異なり、機械語命令と1対1で対応している。命令アーキテクチャが異なればアセンブリ言語も異なる。

命令の種類
  • 移動命令(ロード命令、ストア命令)
  • 演算命令
  • テスト命令(条件チェック)
  • 分岐命令
  • サブルーチン・コール命令
  • スーパバイザ・コール命令
  • コンテクスト・スイッチ命令
  • その他(排他制御命令など)
古い時代のCPUは単純に計算制御のための命令だけを備えていましたが、現在のCPUはリアルタイム制御をしたり、複数の仕事を同時こなすことを考慮し、OSの並行処理のサポートをする命令が用意されています。

移動命令
メモリからレジスタへの移動(ロード命令)、レジスタからメモリへの移動(ストア命令)、メモリからメモリへの移動、レジスタからレジスタへの命令などが考えられます。レジスタはメモリよりも遥かに高速にアクセスできます。汎用レジスタを数多く用意するアーキテクチャは、メモリを利用せずなるべくレジスタ間で演算ができるように考えられた方式でもあります。

演算命令
演算命令は加算、減算、乗算、除算、剰余などがあります。初期の8bitCPUでは乗算、除算などはありません。乗算や除算はシフト命令を利用して、ソフト的に実現するのが一般的でした。また、浮動小数点数の演算も別扱いです。演算ユニットやレジスタも浮動小数点数は別になっています。演算命令は、レジスタ-メモリ間で実行できるものと、レジスタ間のみ許される方式があります。レジスタ-メモリ間で演算できるものは、移動命令を演算命令が含んでいると考えられます。

テスト命令
テスト命令は値を比較する命令です。この命令は基本的には演算命令です。演算結果を残さない演算命令と考えられます。結果はステータスレジスタに反映されます。

分岐命令
基本的な機能はステータスレジスタのフラグの状況に従って、プログラムカウンタの内容を変更することです。これによって条件による分岐(if文やswitch文の機能)を実現します。分岐命令にもアドレシングはあり、絶対アドレスに対する分岐、相対アドレス分岐、間接アドレス分岐などが考えられます。

サブルーチン・コール命令
サブルーチン・コール命令は基本的には分岐命令なのですが、戻り番地保存した上で分岐する必要があります。普通はスタック上に戻り番地を積んで分岐します。戻る場合は逆にスタックの値をポップして、その番地に戻ります。サブルーチン・コール命令には引数を渡す機能や戻り値を受け取るための仕組みをもった命令もあります。このような命令が用意されていると、関数やメソッド呼び出しは効率よく行えることになります。

スーパーバイザ・コール命令
オペレーティング・システムは一般に、ユーザが利用するユーザモードと、システムのリソースを操作するスーパーバイザモードをもっています。システムに操作を依頼する場合には、スーパーバイザ・コール命令とかトラップ命令を用います。これらの命令は現在のレジスタの値をすべて退避し、決められた番地に分岐する命令です。分岐先はシステムが決めており指定することはできません。同じような動作を行う場合に割り込みがあります。外部から割り込み信号が入った場合、CPUはすべてのレジスタをメモリ上のスタックなどに退避し、割り込みアドレスに分岐します。スーパーバイザ・コール命令はソフトウエア割り込みと呼ばれたりもします。この命令が、ユーザモードからスーパーバイザ・モードを移行する唯一の命令です。

コンテクスト・スイッチ命令
マルチタスク処理で、複数のプロセスを扱うために必要な処理(コンテクスト・スイッチ)をサポートする命令です。この命令はスーパーバイザ・モードで利用されます。割り込みやスーパーバイザ・コールがきっかけとなってプロセスの状態がスイッチされます。この処理を効率よく行うための命令です。

命令の方式
  • アキュムレータ・アーキテクチャ
  • 汎用レジスタ・アーキテクチャ
  • スタック・アーキテクチャ
アキュームレータ・アーキテクチャ
アキュムレータを中心において、アキュムレータを対象としてメモリ(主記憶)との間で演算をする命令によって構成される。EDSACやIBM701など初期のコンピュータの多くはこの方式。Intel8080などの初期の8bitマイクロプロセッサ、16bitの8086もこの方式である。メモリのアドレスを指定するレジスタ(インデックス・レジスタ)等は専用のレジスタを用い、専用の命令によって操作する(専用レジスタ・アーキテクチャ)。レジスタが専用化されているので、命令のサイズは小さい。
  • Intel 4004, 8008, 8080
  • Intel 8086
専用レジスタの種類
  • プログラム・カウンタ
  • ステータス・レジスタ
  • スタック・ポインタ
  • フレーム・ポインタ
  • インデックス・レジスタ
  • セグメント・レジスタ
プログラム・カウンタ
次に実行する命令のアドレスを保持するレジスタ。プログラム・カウンタを変更すれば実行アドレスを変更でき、プログラム・カウンタに対する相対命令は、プログラムの位置を自由に変更可能なプログラムの記述ができる。

ステータス・レジスタ
演算によって変化する状態を表現するビットの集まり。条件分岐命令はこの値によってジャンプ先が変わる。0による割り算、演算結果の符号、結果が0かどうか、といったビットが用意されている。PSW(プロセッサ・ステータス・ワード)とか、CC(コンディション・コード)レジスタと呼ばれることもある。

スタック・ポインタ
多くのプロセッサでは、サブルーチン呼び出しの制御のための命令をもっている。サブルーチン呼び出しでは、現在のプログラムカウンタやプロセッサなどの状態をメモリ上のスタックに積んで、サブルーチンにジャンプする。逆に戻りでは、それらの値を復帰することで、呼出し元に戻って制御を続ける。サブルーチンではスタック上に必要な計算データを置くことによって、入れ子の呼び出しや再帰呼び出しも問題なく処理できる。
ローカル変数はスタック上に確保することにより、効率よく記憶管理することができる。汎用レジスタを多く用意する代わりに、スタックをレジスタ代わりに使えるような命令を用意するスタック・アーキテクチャと呼ばれる方式もある。

フレーム・ポインタ
関数やメソッドの呼び出し管理にスタックには、基本的な戻り番地以外に、ローカル変数やレジスタの退避情報などが格納される。これをコールフレームと呼ぶ。コールフレームの末尾はスタックポインタが管理しているが、先頭にあたる部分を保持するのがフレームポインタである。スタックポインタはローカル変数の追加等で位置が動くが、フレームポインタからは確実に戻り番地やレジスタの退避情報を参照できるため、安全に呼び出し元に復帰することができる。

インデックス・レジスタ
間接アドレッシングで利用されるレジスタである。配列などの連続データのアクセスに利用される。汎用レジスタ・アーキテクチャでは汎用レジスタがこの目的に利用できるが、専用レジスタ・アーキテクチャではアドレス操作専用のレジスタが用意されている場合が多い。Motorola MC68000シリーズは汎用レジスタ・アーキテクチャではあるが、データ・レジスタとアドレス・レジスタが別に存在する。これはプロセッサの設計を簡単にするためである。

セグメント・レジスタ
Intelの8086, 80286等で、64KBを超えたメモリアクセスのために導入されているレジスタ。8086ではセグメント・レジスタの値は4ビットシフトされ、アドレスデータに加算されて用いられる。8086は16bitプロセッサだが、アドレスは20bit。20bitのレジスタはないため、直接アクセスできる空間は64KBに限定される。コード・セグメント、データ・セグメントという具合に目的ごとにメモリ空間を分割して利用する形式。

汎用レジスタ・アーキテクチャ
演算に利用できるレジスタが複数あるアーキテクチャ。8本以上あるレジスタをさまざなな目的すべてに利用することができるので、柔軟なプログラミングが可能。アドレス修飾を行うインデックス・レジスタ、スタック・ポインタ、プログラム・カウンタなど専用機能をもつものも同じ命令で操作できる。命令は整理され、柔軟な処理が可能。汎用レジスタ・アーキテクチャにはレジスタとメモリの間の演算を基本とするレジスタ-メモリ方式とレジスタ間の演算を基本とするレジスタ-レジスタ方式がある。レジスタ-レジスタ方式は演算を行うためには必ずメモリからデータをレジスタにロードする必要があり、演算後はレジスタの値をメモリにストアする命令を実行する必要がある。

レジスタ-メモリ方式
    • DEC PDP-11, VAX-11
    • Motorola MC68000, MC68020, MC68030
    • Intel 80386, 486, Pentium, ...
レジスタ-レジスタ方式(ロード/ストア方式)
    • MIPS R2000, R3000, R4000, ...
    • Sun SPARC
    • IBM Power, PowerPC
    • DEC Alpha
    • Intel HP, Itenium
スタック・アーキテクチャ
メモリ上のスタックを汎用レジスタように扱う方式。スタック・ポインタに対する(あるいはフレーム・ポインタ)に対する相対アドレッシングを容易に利用できるような命令セットになっている。物理的なプロセッサも古くはバロースのB5000アーキテクチャ、HPのHP3000などがある。マイクロプロセッサでは、Motorolaの8bitプロセッサMC6809はスタックマシンを意識したアーキテクチャである。物理的なプロセッサではく、仮想マシンにはスタック・アーキテクチャは多く存在する。代表的なものはJava VMである。
スタック・アーキテクチャは汎用レジスタ・アーキテクチャと比較してレジスタをアドシングする必要がないため、コードをたいへん小さくすることが可能である。演算はスタックの上部のデータに対して行うので、オペランドの必要がない命令で操作が可能となる。

レジスタセット

8080のレジスタ

16ビット単位の呼称8ビット単位(上位)8ビット単位(下位)説明
PSWAFlagアキュムレータ(A)とフラグ・レジスタ
BCBC汎用レジスタ
DEDE汎用レジスタ
HLHL汎用兼 間接参照用レジスタ
PC
プログラムカウンタ
SP
スタックポインタ


8086のレジスタ
8080と命令の互換性はないが、8080を意識したレジスタセットになっている。特徴はセグメントレジスタCS, DS, SS, ESである。8086は20bitのアドレスが扱えるが、レジスタはすべて16bit。20bitのアドレス空間を直接アクセスする手段は提供されていない。アドレスはセグメントレジスタの内容を4bitシフトして足した値から得られる。直接アクセス可能な範囲は64KBの空間に限られたので、大容量めもりを利用や、リニアなアドレス空間を意識して作成されたC言語などのコンパイルは困難であった。
現在から考えれば、16bitプロセッサとしても使いやすいものではなく、欠点のほうが目立ったプロセッサであったが、80286の演算性能はそこそこ高く、広く16bitパソコンに利用はされたが、逆にそれがパソコンの発展を妨げたとも考えられている。

80386のレジスタ
80386では8086, 80286のアーキテクチャを大きく見直し、直接的な32bitアドレッシングが可能になっている。16bitのレジスタは32bit化されて、EAX, EBX, ECX, EDX, ESP, EBP, ESI, EDIとなり専用レジスタというより、汎用レジスタ・アーキテクチャになっている。しかし、汎用レジスタ・アーキテクチャとしてはレジスタの数が少なく、自由にレジスタを利用できるようにはなっていない。命令はコンパクトではあるが、レジスタ-メモリ間演算が多くなるため、メモリのキャッシュ技術などが重要になってくる。

データ転送命令
movmov ax,bxaxの内容をbxに
xchgxchg ax,bxaxとbxの内容を入れ替える
算術演算命令
addadd bx,axax+bx → ax
adcadc bx,axax+bx+cf → ax、桁上げがあればcf=1
subsub bx,axax-bx → ax
if ax=bx then zf=1
if ax≠bx then zf=0
if ax>bx then cf=0 and zf=0
if ax≧bx then cf=0
if ax<bx then cf=1
if ax≦bx then cf=1 and zf=1
sbbsbb bx,axax-(bx+cf) → ax、フラグはsubに準ずる
cmpcmp bx,axax-bxを実行、結果は捨てられる
フラグは sub bx, axに同じ
negneg ax-ax→ax、axの2の補数をとる
if ax=0 then cf=0 else cf=1 end-if
decdec axax-1→ax、cf以外のフラグは変化する
incinc axax+1→ax、cf以外のフラグは変化する
div符号無し除算
  1. オペランド=8ビット、例 divb dbyte
  2. ax÷dbyte、 商→al, 剰余→ah
  3. オペランド=16ビット、例 divw dword
  4. dx:ax÷dword、 商→ax, 剰余→dx
  5. オペランド=32ビット、例 divl dlong
  6. edx:eax÷dlong、 商→eax, 剰余→edx
:0による除算、商のオーパフローで例外処理
mul符号無し乗算
  1. オペランド=8ビット、例 mulb dbyte
  2. al×dbyte → ax
  3. オペランド=16ビット、例 mul dword
  4. ax×dword → dx:ax
  5. オペランド=32ビット、例 mul dlong
  6. eax×dlong → edx:eax
積がオペランドのビット数を超えるとcf=of=1
さもなくばcf=of=0となる
pushpush axespの値をデータサイズだけマイナスし、espの指す領域にデータを転送
スタック領域にデータを転送
poppop axespの指す領域からデータを取り出し、レジスタ・メモリに格納、
espの値をデータサイズだけプラスする
スタックからデータ取り出し
分岐命令
jmpjmp 分岐先無条件に分岐する
jcjc 分岐先carry, cf=1ならば分岐
jncjnc 分岐先not carry, cf=0ならば分岐
jeje 分岐先equal, zf=1ならば分岐
jzjz 分岐先zero, zf=1ならば分岐
jne
jne 分岐先not equal, zf=0ならば分岐
jnzjnz 分岐先not zero, zf=0ならば分岐
jcxzjcxz 分岐先ecxレジスタが0なら分岐(short分岐のみ)
jcxzjecxz 分岐先cxレジスタが0なら分岐(short分岐のみ)
jacmp bx, ax
ja 分岐先
above, cf=0 and zf=0で分岐
if ax>bx then 分岐
jnbe (below)に同じ
jbcmp bx, ax 
jb 分岐先
below, cf=1で分岐
if ax<bx then 分岐
jnae (not above or equal)に同じ
jaecmp bx, ax
jae 分岐先
above or equal, cf=0ならば分岐
if ax≧bx then 分岐
jnb (not below)に同じ
jcxzjcxz 分岐先ecxレジスタが0なら分岐(short分岐のみ)
jbecmp bx, ax
jbe 分岐先
below or equal, cf=1 or zf=1 で分岐
if ax≦bx then 分岐
jna (not above)に同じ
jcxzjecxz 分岐先cxレジスタが0なら分岐(short分岐のみ)
jcxzjcxz 分岐先ecxレジスタが0なら分岐(short分岐のみ)
jcxzjecxz 分岐先cxレジスタが0なら分岐(short分岐のみ)
loop 命令、ecxあるいはcxをdecして、そのときのフラグによりshort分岐する
以下の表の命令(loopの後に何も付かない)あるいは最後にdをつけるとecxを使用
最後にwをつけるとcxを使用
例 loop, loopd, loope, looped…ecxを使用、loopw, loopew…cxを使用
looploop 分岐先ecxあるいはcxが0でなければ分岐(ループ)
loope
(loopz)
loope 分岐先ecxあるいはcxが0でなく、zf=1ならば分岐(ループ)
loopne
(loopnz)
loopne 分岐先ecxあるいはcxが0でなく、zf=0ならば分岐(ループ)
コール命令
callcall サブルーチン名サブルーチン呼び出し
retretサブルーチンからの復帰
論理演算命令
andand bx, axbx and ax → ax、ビット毎の論理積
oror bx, axbx or ax → ax、ビット毎の論理和
notnot axnot ax → ax, ビット毎のnot(反転)
xorxor bx,axbx xor ax → ax、ビット毎の排他的論理和
testtest bx, axbx and axの実行、結果は捨てられる
フラグは結果にしたがいセットされる
シフト命令、オペランドのシフトビット数は%clあるいは即値で与える
shlshl %cl, axshift logical left, 論理左シフト
lsbに0を補いながらclビットだけ左シフト(cl≦31)
salsal %cl, axshift arithmetic left、算術左シフト
shl命令と同一の命令
shrshr %cl, axshift logical right, 論理右シフト
msbに0を補いながら、clビットだけ右シフト(cl≦31)
sarsar %cl, axshift arithmetic right, 算術右シフト
msbを変化させずclビットだけ右シフト、msbは符号ビット
ローテート命令、ビット数は%clあるいは即値で与える
rolrol %cl, axrotate left, 左ローテート
%clビットだけ右に回転、msb→lsbとaxの左端と右端が隣り合うように回転
rorror %cl, axrotate right, 右ローテート
%clビットだけ右回転、lsb→msbと、右端が左端に隣り合うように回転
rclrcl %cl, axrotate through carry left、左キャリーローテート
msb→cf→lsbと、cfを間に挟んで左回転
rcrrxr %cl, axrotate through cf right, 右キャリーローテート
lsb→cf→msbとcfを間に挟んで右回転
フラグ操作
stcstcset carry flag, 1→cf
clcclcclear carry flag, 0→cf
cmccmccmplement carry flag, cfの反転
pushfpushfレジスタeflagsの下位16ビットをスタックへpush
eflags全部をpushする場合にはpushfdを使用
popfpopfスタックに退避された内容(16ビット)をeflagsにpop
popfdではeflags全部のビットをpop
ただし、保護されているフラグは変化しない

Ċ
Hideto Sazuka,
2009/10/22 17:18
Comments