アセンブラー言語におけるプログラムの書式と基本的なアセンブラー命令が理解できるとプログラムを書く準備が整います。しかし、アセンブラー・プログラムではCPUに直接指示を与えるため、プログラムを書くためにはCPUと関連するハードウェアに関する知識がどうしても必要です。ここでは、アセンブラー・プログラミングに関するCPUの仕組みや実際にCPUを動かすためのマシン命令の基礎について解説します。
CPUとPSW
S/360とS/370以降ESA/390迄とそれらの互換アーキテクチャーでは、CPUは32ビットCPUです。命令セットはIBM社独自のものが採用されています。この講座では現在のzアーキテクチャーの前身である32ビットCPU命令を中心に解説します(*1)。zアーキテクチャーではCPUは64ビットになり命令セットも大幅に拡張されましたが、従来のアーキテクチャーでサポートされている命令セットはそのまま使用できます。
PSW(Program Status Word:プログラム状態語)は、CPUで現在動いているプログラムの走行状態を示す64ビットの特殊なレジスターで、CPU動作を制御する基本的な情報が格納されています。最初からPSWの全ての情報について覚える必要はなく、アセンブラー・プログラミングを行う上で、あるいはデバッグ作業を行う上で必要なものから最初に覚えればいいでしょう。

PSWには「078D0000 00006192」のような値が格納されていたりします。PSWの後半32ビットはNSI(Next Sequential Instruction)と呼ばれる、次に実行する命令の仮想アドレスを示すフィールドです。32ビット目、NSIの先頭ビットはアドレッシング・モードです。プログラムが24ビットあるいは31ビットのどちらのモードで動いているかを示します(ビットが1なら31ビット・アドレッシング・モード)。NSIの残りの31ビットに命令アドレスが入ります。NSIが示すのは今実行している命令ではなく、次に実行する命令のアドレスであることを覚えて下さい。
PSWの前半32ビットは、CPUがプログラムをどのように実行するかの各種の制御ビットで構成されます。最初は2バイト目のアクセス・キーとPビット(問題プログラム状態を示す)を覚えておけばいいでしょう。PSW値の例で示した「078D…」のx8Dは、8がPSWキーとも呼ばれるアクセス・キーで、メモリー側の記憶保護キーと比較されてPSWキーとメモリー側のキーが一致していれば書き込みができます。次のDが問題プログラム状態を示します(ユーザーの業務アプリケーション・プログラムは基本的に問題プログラム状態で実行される)。ここがCなら監視プログラム状態(スーパーバイザー・モード)です。正確にはPビットだけでなく他のビットとの組み合わせでDやCになるのですが、滅多なことでは他のパターンにはなりません。先ずは、NSI、アクセス・キー(PSWキー)及びPビットを覚えればいいでしょう。3バイト目の前半4ビットのうち、後ろ2ビット(ビット18~19)は命令の結果を示す条件コードですが、実際PSWの条件コードを見てデバッグするようなことはあまりありません。
レジスター
CPUには16個の汎用レジスター(General Purpose Register)があります。GPRまたはGRと略され32ビットの長さを持ち、それぞれ0~15の番号で識別されます。汎用レジスターは、命令オペランドのアドレス計算、整数演算、論理演算、実行結果の格納などに用いられます。汎用レジスターの他にも、CPUの制御に使う制御レジスター、クロスメモリー機能で使用するアクセス・レジスター、浮動小数点演算に使う浮動小数点レジスター(64ビット)があります。汎用レジスター以外は一般のプログラムでは基本的に使用しません。zアーキテクチャーではCPUに合わせて汎用レジスターと制御レジスターは64ビットに拡張されていますが、これまでの32ビットCPU命令を使う場合はその違いを意識する必要はありません。
1 2 3 4 5 |
+----------+---------+---------+----------+ GPR I 0000 0000 0000 0000 0000 0000 0000 0000 I +----------+---------+---------+----------+ * * bit0 bit31 |
レジスターのいちばん左端がビット0です、以降右へビット1、2、3と続き右端がビット31です。CPUが複数個実装されているマルチCPU構成のプロセッサーではレジスターは各々のCPUにありますが、プログラムはどのCPUのレジスターを使うかを意識する必要はありません。
主記憶装置(メモリー)へのアクセス
主記憶装置はプログラムとプログラムが使用する様々なデータを格納します。S/370以降のアーキテクチャーでは仮想記憶システムなので、我々が作り動かすプログラムはすべて仮想記憶上で動作します。実記憶(リアル・メモリー)を直接アクセスすることはありません。以降の解説も主記憶装置あるいはメモリーはすべて仮想記憶装置を指します。
主記憶には先頭からアドレス(番地)が振られ、バイト単位でアクセスできます。アドレスはx00000000から始まりx00FFFFFF(16MB)へ、さらにx01000000からx7FFFFFFF(2GB)へと続きます。
アドレッシング
メモリーをアクセスする命令は、オペランドで操作の対象となるデータがメモリー上のどこにあるかを指し示す必要があります。これがアドレス表現で、命令実行時に実際のメモリー・アドレス(実効アドレス)が計算(アドレス計算と呼ばれる)されます。CPU命令では、メモリー上のアドレスは基本的に以下の2つの方法でアドレスが計算されます。
ベース+変位
レジスターに格納されたベース・アドレス(基底アドレス)の値に、変位(displacement)を加えて主記憶アドレスを求めます。変位は、ベース・アドレスから何バイト離れたところを示すかの数で0~+4095の値が使えます。変位は命令のオペランド中に固定値で示されます。
ベース・アドレスを格納したレジスターのことをベース・レジスターと呼びます。「ベース+変位」の方式では、4ビットでベース・レジスター番号、12ビットで変位を示し、アドレス部の命令オペランドは16ビットになります。ベース・レジスターにx6000が入っていて、命令オペランドで変位がx128(296)であれば主記憶アドレスはx6128です。
「STCK 120(12)」のように、ベース+変位の方式では記憶域オペランドを「120(12)」のように表現します。書式は、「命令 変位(ベース・レジスター番号)」です。この例では、12番レジスターの内容に10進数の120を加えた数が実際のメモリー・アドレスになります。人間がいちいち変位を計算するのは面倒なので、普通は「変位(ベース・レジスター番号)」を直接書くのではなくラベル名で示します。「STCK AREA」のようにオペランドにラベル名が書かれた命令は、アセンブラーによって「STCK 120(12)」と翻訳されます。
命令を翻訳する時、何番のレジスターをベース・レジスターに使うかはUSING命令で決まります。「USING LABELA,8」とすれば、LABELAがベース・アドレスでベース・レジスターが8番です。「USING *,12」とすれば、USING命令を書いた場所がベース・アドレスでベース・レジスターは12番です。基本的にプログラムは最低でも1つのベース・レジスターを使います。通常はプログラムの先頭をベース・アドレスにします。ベース・レジスターは0以外であればどのレジスターでもいいのですが、1と2、13~15番は避けて3~12番の中から選びます。具体的にどれを使うかは最初に習ったり、手本にしたりしたプログラムのスタイルで決まることが多いようです。一般的にはレジスター12番がよく使われますが、レジスター3番が使われている例もあります。一般のプログラムであれば、7とか8等の中間の番号のレジスターが使われることはまずありません。
アセンブラーは、USING命令で指定したベース・レジスターを使って命令を翻訳しますが指定されたレジスターにベース・アドレス値を設定することまではしません。アセンブラー命令とCPU命令の違いがわかっていないとこのような勘違いしてしまうかも知れません。アセンブラーのUSINGは、命令オペランドをラベル名で書けるようにするだけです。USING命令で指定したベース・レジスターに正しいベース・アドレス値を設定するのはプログラマーの仕事で、どのプログラムも最初にこれを行います。
ベース+インデックス+変位
ベース+変位のアドレス計算に、さらにインデックス・アドレス(指標アドレス)を加えたものです。レジスターに格納されたベース・アドレス(基底アドレス)とインデックス・アドレスの値に変位(displacement)を加えて主記憶アドレスを求めます。変位は、ベース+インデックス・アドレスから何バイト離れたところを示すかの数で0~+4095の値が使えます。変位は命令のオペランド中に固定値で示されます。
インデックス・アドレスを格納したレジスターのことをインデックス・レジスターと呼びます。
「ベース+インデックス+変位」の方式では、4ビットでベース・レジスター番号、4ビットでインデックス・レジスター番号さらに12ビットで変位を示し、アドレス部の命令オペランドは20ビットになります。ベース・レジスターにx6000が、インデックス・レジスターにx2000が入っていて、命令オペランドで変位がx128(296)であれば主記憶アドレスはx8128です。
「LA R1,120(10,12)」のように、ベース+インデックス+変位の方式では記憶域オペランドを「120(10,12)」のように表現します。書式は、「命令 変位(インデックス・レジスター番号,ベース・レジスター番号)」です。この例では、10番及び12番レジスターの内容に10進数の120を加えた数が実際のメモリー・アドレスになります。なお、アセンブラーではラベル名のみでオペランドを指定した場合、インデックス・レジスターは使用されません。インデックス・レジスターを使うには「LA R1,AREA(10)」のようにインデックス・レジスターを使用することを明示する必要があります。
インデックス・レジスターは全ての命令で使えるわけではなく、使用できる命令が決まっています。また、ベース+インデックス+変位方式の命令でも、インデックス・レジスターの使用は任意であって必ずしも指定する必要はありません。インデックスを使わずにベース+変位でのみアドレスを計算させるなら、インデックス・レジスター番号を省略するか(「120(,12)」)0を指定(「120(0,12)」)します。S/370では、レジスター0番はベース・レジスターとインデックス・レジスターのどちらとしても使用することはできません。オペランドで指定した0は、0番レジスターではなく、数としての0そのものを意味します。「STCK 120(0)」「LA R1,120(0,0)」はいずれも主記憶アドレス120番地を指します。これはとても重要なことなのでよく覚えておいて下さい。

S/360やS/370アーキテクチャーにおける命令のオペランド・アドレスは、プログラム内に直接アドレスの値そのものを持たない間接アドレッシング方式です。ベース・レジスターの値を変えることで、プログラムを、例えば主記憶の5000番地、10000番地あるいは32080番地など、主記憶のどこにでも配置できるようになります。これによって、プログラムはリロケータブル(再配置可能)な性質を持つことができます。仮想記憶システムが登場する以前の、実記憶上でプログラムを直接動かしていた時代では大きなアドバンテージでした。実効アドレスを求めるためのアドレス表現が複雑に見えますが、実記憶装置をいかに効率よく使い、いかに少ない長さでオペランドを表現するかと言うことで考え抜かれた設計なのです。
アドレッシング・モード
レジスターは32ビットなのでフルビット使えば実効アドレスの範囲は0~4GB-1ですが、S/360システムでは下位24ビット(3バイト)を主記憶アドレスに使用しました。24ビットでは0~16MB-1です。当時のメモリー容量は数十~数百KB、大きくても数MBでしたから、32ビット全部をアドレスに使うよりはその1部を他の目的に使おうと言う発想は自然でした。S/370システムでも24ビットアドレスはそのまま踏襲され仮想記憶も16MBの大きさを持ちました。基本的にプログラムはx00000000~x00FFFFFFの範囲でしか主記憶をアクセスできません。これが24ビット・アドレッシング・モードです。先頭の1バイトは主記憶アドレスとしては無視されるため、メモリー容量が小さかった当時はメモリーを節約するために、この部分にさまざまな制御用の情報をフラグバイトとして持ちました。4バイトあればXL1’フラグ’+AL3(アドレス)として使われることも多く、MVSでは今でも随所にこのパターンが残っています。
やがて主記憶容量が16MBではどうにもならなくなったため、仮想記憶の大きさは31ビットで表現できる2GBに拡張されました。32ビットにしなかったのは、24ビットモード・プログラムに対する互換性のためです。32ビットの内、先頭ビットをアドレッシング・モードの判定用に用いて残りの31ビットを主記憶アドレスに使いました。この場合、プログラムはx00000000~x7FFFFFFFの範囲で主記憶をアクセスできます。これが31ビット・アドレッシング・モードです。PSWのアドレス部(後半32ビット)の先頭ビットが0であれば24ビット・モード、1であれば31ビット・モードと判別されます。
1 2 |
x00006000 → 24ビットモード x80006000 → 31ビットモード |
特別に指定しない限り、アセンブラー言語で作成したプログラムは24ビット・アドレッシング・モードのプログラムになります。先ずは、基本となるこのモードでプログラミングを覚えて、必要ならそれから31ビット・アドレッシング・モードへ進む方がいいでしょう。新しいアプリケーションをこれからアセンブラー言語で作るなら、最初から31ビット・モード(あるいは64ビット・モード)のプログラムで覚えてしまってもいいのですが、今日の実務でのアセンブラー・プログラミングは、過去に作られた古いプログラムの保守や他言語への書き換えなどが主ですから24ビット・アドレッシング・モードだけでも十分な場合も多いです。31ビット・アドレッシング・モードについては、必要に応じて理解して使い分けられるようにすればいいでしょう。また、64ビット・アドレッシング・モードを必要とする場合の前提となる知識でもあります。
境界調整
データや命令を、メモリー上の整数倍アドレスから配置することを境界調整(バウンダリー合わせ)と言います。例えば、フルワードの整数は4の倍数で始まるアドレス、ハーフワード整数は2の倍数で始まるアドレスにデータを揃えることです。DCやDS命令を使用して定義されたデータは、アセンブラーが必要に応じて境界調整します。
CPUにはバイト式オペランド機構(byte oriented operand)が備わっているため、フルワード整数が2の倍数や奇数のアドレスで始まっていてもエラーにならずに正しく実行されます。しかし、きちんと境界調整がなされている場合に比べて若干のオーバーヘッドが生じます。なお、命令は必ずハーフワード境界に調整されていなければなりません。命令が奇数番地に置かれると、その命令を実行しようとした時にプログラムは指定例外(S0C6)でABENDします。命令と命令の間にキャラクター型のデータを挟むような場合は特に注意が必要です。アセンブラーのDS 0HやCNOP命令で明示的に境界調整を行います。
とりあえずは実行命令の列の中でフルワード境界に調整する必要があるなら「CNOP 0,4」、データ域の直後に命令を置く場合は「DS 0H」と覚えておけばいいでしょう。命令については、データ領域の直後であろうがなかろうが、命令自身にラベルを付けるのではなく直前にラベル付きのDS 0Hを書いてその直後に命令を書くことを勧めます(*1)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- : CNOP 0,4 ADJUST TO FULL WORD BOUNDARY BAL 13,*+4+72 AROUND GPRS SAVE AREA DC 18F'-1' OUR GPRS SAVEAREA : : B LABELX DO PROCEDURE 'X' DC CL3'ABC' DEFAULT VALUE SPACE , LABELX DS 0H L 0,FWAREA LOAD INPUT TEST VALUE LABELX L 0,FWAREA この書き方は誤りではないが良くはない : |
命令の形式
CPU命令は、2、4又は6バイトのいずれかの長さを持ち、命令によってその長さが決まっています。オペランドの内容によって長さが変わることはありません。また、命令には形式があって命令長やオペランドのフォーマットなどは形式によって定められていて、同じ命令なら形式や長さがオペランドの内容によって変わることはありません。現在のzアーキテクチャーでは、命令セットが増えて命令の形式も約40種類近くもありますが、ここでは基本となる6つの命令形式を紹介します。
RR形式
1 2 3 4 |
+---------+----+----+ I OP-CODE I R1 I R2 I +---------+----+----+ 0 7 8 11 12 15bit |
命令長は2バイトで、先頭1バイトが命令コード、オペランドは1バイトで第1オペランド、第2オペランドのレジスター番号をそれぞれ4ビットで示します。
レジスター同士のデータ移動や演算処理に使われる命令で、オペランドに記憶域が指定される場合は、そのアドレスがレジスターに直接格納されます。これはR形式アドレスとも呼ばれます。
RX形式
1 2 3 4 |
+---------+----+----+----+--------------+ I OP-CODE I R1 I X2 I B2 I D2 I +---------+----+----+----+--------------+ 0 7 8 11 1215 1619 20 31bit |
命令長は4バイトで、先頭1バイトが命令コード、オペランドは3バイトで4ビットの第1オペランドのレジスター番号、4ビットの第2オペランドのインデックス・レジスター番号、4ビットの第2オペランドのベース・レジスター番号、12ビットの第2オペランドの変位を示します。
レジスターと主記憶間のデータ移動や演算処理に使われる命令で、第2オペランドのアドレスは「ベース+インデックス+変位」でアドレス計算されます。これはRX形式アドレスとも呼ばれ、第2オペランドは「d2(x2,b2)」のように表現されます。
RS形式
1 2 3 4 |
+---------+----+----+----+--------------+ I OP-CODE I R1 I R3 I B2 I D2 I +---------+----+----+----+--------------+ 0 7 8 11 1215 1619 20 31bit |
命令長は4バイトで、先頭1バイトが命令コード、オペランドは3バイトで4ビットの第1オペランドのレジスター番号、4ビットの第3オペランドのレジスター番号、4ビットの第2オペランドのベース・レジスター番号、12ビットの第2オペランドの変位を示します。
複数レジスターと主記憶間のデータ移動やシフト、分岐処理などに使われる命令で、第2オペランドのアドレスは「ベース+変位」でアドレス計算されます。これはRS形式アドレスとも呼ばれ、第2オペランドは「d2(b2)」のように表現されます。
SI形式
1 2 3 4 |
+---------+---------+----+--------------+ I OP-CODE I I2 I B1 I D1 I +---------+---------+----+--------------+ 0 7 8 15 1619 20 31bit |
命令長は4バイトで、先頭1バイトが命令コード、オペランドは3バイトで8ビットの第2オペランドの即値、4ビットの第1オペランドのベース・レジスター番号、12ビットの第1オペランドの変位を示します。
1バイトの固定値と主記憶間でのデータ移動や論理演算処理などに使われる命令です。第2オペランドは「d2(b2)」のように表現され、記憶域は「ベース+変位」でアドレス計算されます。
SS形式
1 2 3 4 5 6 7 8 |
+---------+---------+----+--------------+----+--------------+ I OP-CODE I L I B1 I D1 I B1 I D1 I +---------+---------+----+--------------+----+--------------+ 0 7 8 15 1619 20 31 3235 36 47bit +---------+----+----+----+--------------+----+--------------+ I OP-CODE I L1 I L2 I B1 I D1 I B1 I D1 I +---------+----+----+----+--------------+----+--------------+ 0 7 8 15 1619 20 31 3235 36 47bit |
命令長は6バイトで、先頭1バイトが命令コード、オペランドは5バイトで8ビットの第1オペランドの長さ(又は4ビットずつの第1及び第2オペランドの長さ)、4ビットの第1オペランドのベース・レジスター番号、12ビットの第1オペランドの変位、4ビットの第2オペランドのベース・レジスター番号、12ビットの第2オペランドの変位を示します。
主記憶間でのデータ移動やパック10進数の演算処理などに使われる命令で、第1及び第2オペランド各々がRS形式アドレスで設定され「d2(b2)」のように表現されます。
S形式
1 2 3 4 |
+-------------------+----+--------------+ I OP-CODE I B2 I D2 I +-------------------+----+--------------+ 0 15 1619 20 31bit |
命令長は4バイトで、先頭2バイトが命令コード、オペランドは2バイトで4ビットの第2オペランドのベース・レジスター番号、12ビットの第2オペランドの変位を示します。(実際のオペランドは1つしかありませんが、S形式では第2オペランドとして扱われます)
TODクロック値の格納等、主にプロセッサー制御機構に関する取り扱い命令です。また、PSWキーの変更やチャネルへの入出力指令等、特権命令の多くがこの形式を持ちます。
命令形式を知らなくてもアセンブラー言語でのプログラミングはできます。ただし、デバッグ作業でダンプ・リストの解析をしたり、意図しない実行結果となった時に改めてアセンブル・リストをトレースする際には命令形式を理解していることは有用です。特に、CPU命令のオペランドを間違った場合でも構文自体が正しければアセンブラーはエラーにしません。そのため、ソース・プログラムだけを追っていても誤りに気付けないことは多いのです。命令形式がわかっていれば、機械語コードに翻訳されたCPU命令を見て、元のソースの命令記述の誤りに気付けるようになります。
命令形式を理解し覚える早道は、とにかくアセンブル・リストを見ることです。翻訳された機械語コードと自分が書いた命令を対比させて読む癖をつけます。デバッグには必ずアセンブリー・リストを使い、元のソース・プログラムはプログラム修正作業以外では絶対に見ないを徹底すれば、命令形式や具体的な命令コードはすぐにわかるようになります。実際にアセンブラー・プログラミングをやって見ればわかりますが、ツールであっても実際の業務で作成するプログラムであっても使用する命令の種類はそう多くないのです。せいぜい数十個です。これはプログラムの規模にあまり影響を受けません。また、機械語コードから命令記述をイメージするハンド逆アセンブルができるようになるとデバッグ作業の効率は飛躍的に向上します。