02.アセンブラー言語の概説(コーディングの基礎)

プログラムの書式

アセンブラー・プログラムは1行が80バイトです。LRECL=80の固定長レコードのデータセットを作成してそこへ書いていきます。区分データセットのメンバーとして登録するのが普通ですが、ちょっとしたプログラムや命令の動きだけを確認したいような時は、アセンブルJCL内にSYSINデータとして直接コーディングしてもいいでしょう。

アセンブラーではラベル(名前)は第1桁目、命令はラベルがあればラベルの後ろに1文字以上の空白を置いてから、ラベルがなければ第2桁目以降から書けます。オペランド(命令のパラメーター)は命令の後ろに1文字以上の空白を置いてから書けます。オペランド(オペランドがない命令では命令)の後ろに1文字以上の空白を置けば、以降行末までコメントも書けます。オペランドが1行に書ききれない時は、72桁目に空白以外の文字を置き、続きのパラメーターを次の行の16桁目から(コメントは17桁目以降から)継続できます。
アセンブラー言語では最低限のルールを守れば基本的に自由コーディングできます。しかし、行によって命令の開始桁が違っていたり、人によってスタイルが変わると見にくいので伝統的な決まり事があります。なお、73桁目以降はSEQ番号ですが使わなくてもかまいません。

1~8桁目はラベル。命令は10桁目からオペランドは16桁目から書きます。MVSアセンブラー・サービスのマクロなど、命令が6文字以上ある場合はパラメーター(オペランド)開始位置もそれに合わせてずらします。その場合でも継続行は必ず16桁目からでなければなりません。現在の高水準アセンブラーではロングネームも使えるので必ずしもこのルールに沿えませんが、伝統的な基本として知っておいてください。
コメントにはいろいろスタイルがありますが、第41桁目、40桁目、36桁目などから書かれることが多いでしょう。行全体をコメントにするなら1桁目に*を置きます。オペランドがない命令の文にコメントを書く場合は命令とコメントの間にカンマ記号を置く癖をつけるといいでしょう。

アセンブラー言語ではコメントはとても重要です。命令文ごとにコメントを付けるのが良いのですが、単なる命令自体の解説のようなコメントばかり続くと、くどくなるので注意します。何をしてるのかや、何のためにその命令を書いているのか等を残すように心がけるといいでしょう。上記のサンプルのように*記号などでコメント欄をカプセルのように囲むこともよく行われます。きちんとしたコメントは十分なドキュメントにもなります。商用プログラムではコメント・カプセルの中にドキュメント自体を書いてしまうことも少なくありません。
形を繕うための外部ドキュメントにさしたる意味はありませんが、プログラム内のきちんとしたコメントはプログラムの保守を行う上でとても役に立ちます。商用プログラムの場合、どんなに素晴らしいものでもコメントがまったく無いようなプログラムは0点と言っても過言ではありません。アセンブラー言語でプログラミングを行う場合は、学習レベルのプログラムを書くときからコメントを付ける癖をつけることを勧めます。

プログラムはCSECTで始まりENDで終わる

プログラムの始まりはCSECT命令で示します。CSECT命令の他にもSTART命令というのもあり、言語の機能では最初のセクション開始がSTART、2番目以降のセクション開始がCSECTとされますが、特別な理由がない限りSTARTを使う必要はありません。最初であろうが2番目以降であろうが、セクションはCSECTで始めると覚えていいです。
セクションとはプログラムを構成する要素のことで、実行される命令やデータが展開される制御セクション(Control Section)、外部領域内をフィールドに分割して名前で参照できるようにする見かけセクション(Dummy Section)などがあります。CSECTは制御セクションのことで、実際に命令やデータが展開されてオブジェクト・モジュールとして出力される部分です。
プログラムの終了はEND命令で示します。END命令にはプログラムの実行開始位置を指定することもできます。省略すればCSECTの先頭から実行されます。実行開始位置をセクションの途中からにする手法はさほど難しいものではありませんが、慣れるまではわかりにくいので、例外を除きプログラムはセクションの先頭から実行されるものなのだと覚えていいでしょう。

ロケーション・カウンター

ロケーション・カウンターは、オブジェクト・モジュール内の命令やデータがCSECTの先頭からどれだけ離れているかを示すもので、オフセット、アドレス、番地などとも呼ばれます。あくまでもプログラム(CSECT)の先頭からの相対アドレスです。プログラム実行時にロード・モジュールが仮想記憶域にローディングされると、そのローディング・アドレスが加算されて仮想記憶上の主記憶アドレスを形成します。ロケーション・カウンターは命令やデータの長さに応じてアセンブラーが適切な番地を計算しますが、必要に応じてプログラマーが変更することもできます。これによってデータ領域の再定義(Re-define)が可能になります。

アセンブラー命令

アセンブラー言語では命令は大きく3種類に区別されます。1つ目は「CPU命令」です。マシン命令、機械命令とも呼ばれ、CPUの1つ1つの動作を指示するものです。2つ目は「マクロ命令」です。複数の命令をまとめたもので、繰り返し実行する処理などをいちいち書かなくてもマクロ名を書けば対応する命令列に展開してくれるものです。MVSのアセンブラー・サービス(API)などの多くは、マクロ命令を使って呼び出します。IBMメインフレームのマクロは非常に強力な機能を持っていて、活用すれば高級言語並みのコーディングが可能になります。CPU命令とマクロ命令(1部例外を除く)は最終的にオブジェクトモジュールになります。3つ目が「アセンブラー命令」です。アセンブラー命令は実際にCPUで実行されるものではなく、アセンブラーに対してアセンブル時の動作を指示するものです。いろいろとありますが覚えなければいけないものはそれほど多くありません。

CSECTとEND

CSECTが制御セクション(プログラム)の開始、ENDが終了を示します。1つのソース・プログラム内に複数のCSECTを持つこともできますが、CSECT単位にソース・プログラムのメンバーを分けて作成しバインダー(リンケージ・エディター)でまとめる方がわかりやすいでしょう。

USINGとDROP

ベース(基底)レジスターの設定・解除を行います。ベース・レジスターとはプログラム内で分岐先やデータ・フィールドを名前で指し示す際に、基本となるアドレスがどのレジスターに入っているかをアセンブラーに知らせるために用いられます。通常は、プログラムの先頭アドレス(基底アドレス)が格納されるレジスター番号を指定します。1つのベース・レジスターでアドレスできる範囲は4096バイトです。プログラムが4KB以上の大きさを持つ場合、4KB毎に異なるベース・レジスターを用います。USING命令はベース・レジスターの設定を行う命令で、オペランドにベース・アドレスとそのベース・アドレスが格納されたレジスター番号を指定します。DROP命令は、設定したベース・レジスターの解除を行います。
S/370アセンブラーではベース(基底)レジスター、ベース(基底)アドレスの考え方は重要なので章を改めて解説しますが、ここではプログラムを書く場合は最低1つのベース・レジスターを必要とするのだと言うことを覚えておけばいいでしょう。

DC、DSとORG

定数または変数の定義を行います。一般にDC命令は定数、DS命令は変数を定義するものとして使用されます。しかしながら、CPUは定数と変数を区別しません。単にデータ領域、データ・フィールドをプログラム内に定義する命令と考えればいいです。定数で定義しても、命令で書き込みをすれば内容は変更できますから実際は変数として扱えます。プログラムの記述としては「定数」と捉えることができますが、実態は「初期値付き変数」で定義したデータ域に初期値を設定するのがDC命令です。初期値を必要としなくてもDC命令を使う癖をつけるといいでしょう。デバッグし易くなります。数値なら0を、文字なら空白を与えます。

具体例で示します。DC命令では領域内の値がアセンブリー・リストに出てきます。DS命令ではアドレスしか出ません。DC命令ならデータ域の構造がリストを見れば直感で感じ取れます。DS命令だとデータ型を見ていちいち考えなければなりません。簡単なプログラムではメリットが感じにくいですが、プログラム内のデータ・フィールドが増えてきたり、データ構造が複雑になってくると、このようなこともデバッグを楽にする方法の1つになるのです。

DC命令では、オペランドで定数のデータ型、長さ、複写回数、初期値を設定します。

複写因数は、同じ定数を繰り返し定義する時に指定します。繰り返された定数は連続した領域として定義されます。型は定数の種類を示し、文字型、16進数値型、整数型、パック10進数型、アドレス型などがあります。長さは長さモディファイアとも呼ばれ、型を修飾する目的で指定します。長さ以外の修飾もできますがここでは解説しません。文字型であれば型だけでは1バイトしか定義されないので、連続した10文字を定義したければCL10と記述します。整数も長さを指定でき、F型定数はフルワードで4バイトが定義されますが、FL3とすれば3バイトの整数として定義できます。
アドレス型は、その名の通りアドレスを定義するために使わます。プログラム内のラベル名を値として指定すれば、そのラベルが示す番地が定数として定義されます。アセンブルの時点ではCSECTの先頭からの相対アドレス(ロケーション・カウンター値)がセットされ、バインド時に結合される他のモジュールの大きさや結合順序によって更に調整されます。そして実行時に、ローディングされた位置の仮想記憶アドレスが加算されて実際の主記憶アドレスが設定されます。これらの調整は、アセンブラー、バインダー(リンケージ・エディター)およびMVSのコンテント・スーパーバイザー(CSV)によって自動的になされるため、プログラマーは何も考えなくていいです。A型アドレス定数は自モジュール内のラベルを参照する場合に用いられ、V型アドレス定数は外部モジュールの入口点を参照する(外部サブルーチンを呼び出す)際に用いられます。アドレス定数のように数値をフェーズ毎に変更しなければならないものは、再配置ディクショナリー(RLD)という制御情報によって管理されます。RLDは、オブジェクト・モジュールにもロード・モジュールにも存在します。

アセンブラー言語で使用できる定数・変数型(抜粋)
長さ 説明 備考
C 1byte 文字領域(バイト域)を定義する。 長さはモディファイアで最大256バイトまで伸ばせる。それを超える場合は複写因数を利用する。CL1024とはできないが1024Cとならできる。1024CL8も可能(計8KBの大きさで領域が確保される)
X 1byte 16進数を定義する。 0~Fの16個の16進数を指定できる。16進数2桁で1バイトとなり、x00~xFFまでのすべてのビットパターンを定義できる。
F 4byte フルワードの整数を定義する。 長さモディファイアを使い1~3の範囲で長さの調整が可能。長さを省略した場合、フィールドはワード境界に調整される。
H 2byte ハーフワードの整数を定義する。 しばしばDS 0Hとして命令がハーフワード境界に置かれるようなラベル定義にも使われる。複写因数0は境界調整だけが行われ、データは設定されない。
Y 2byte ハーフワード定数をラベルで定義する。 値は”で囲まれた数値でなくラベル名で定義したい場合などに使う。Y(MAXVALUE)とすればMAXVALUEに100と言う値が設定されてる時、H’100’と同じことになる。
D 8byte ダブルワードの浮動小数点を定義する。 実戦では浮動小数点の定義よりは、ダブルワードの作業用フィールドを作成するために用いられることが多い。
P nbyte パック10進数を定義する。 長さモディファイアで任意の長さの10進数フィールドを定義できるが、長さを省略すれば指定した10進数の桁数に応じて適切な長さが取られる。
A 4byte 命令ラベルや定数のアドレスを定義する。 有効範囲は自モジュール内。フルワード定数をラベルで定義する場合にも利用される。
V 4byte 外部モジュールの入口点アドレスを定義する。 外部サブルーチンの呼び出しで利用する。
C、X、F、Hなどが基本中の基本となるデータ型です。

ORG命令は、ロケーション・カウンターを変更する命令です。主にデータ領域の再定義(Re-define)を行うために使用されます。

最初のORG命令でロケーションカウンターをEXECPARMの先頭に戻し、102バイトフィールドを、2バイトのLENGTHと100バイトのDATA領域として再定義しています。その後に、ロケーション・カウンターを元にに戻しています。ORG命令をオペランドなしで記述すれば、ロケーション・カウンターはそれまでの最高値に再設定されます。これは、ロケーション・カウンターを元に戻すことと同じです。COBOLなどではデータ領域のRE-DEFINEはよく使われますが、アセンブラーでも簡単にできます。

リテラル定数とLTORG

リテラル定数は、DC命令を使わずに命令のオペランド内でダイレクトに定数を指定するために用いられます。DCの代わりに=を使います。同じ内容の定数であれば重複して定義されることはなく、以前に定義されたものが再使用されます。

リテラル定数は、通常END命令によってすべての命令とデータの後ろに展開されます(この例ではPACK2の後ろ)。リテラル定数が集められ展開される場所がリテラル・プールです。LTORG命令を使えば、任意の位置にリテラル・プールを置くことができます。サイズの大きなプログラムなど、ベース・レジスターがアドレスできる範囲との関係で、プログラマーがリテラルプールの位置を調整したい場合に用いられます。

EQU

EQU命令は式や数値に名前を付けるために使われます。よく使われるのがレジスター番号の表記です。

EQU命令で数値に名前を付けて定義し、命令ではその名前で値を参照するようにすれば、計算に使う数値が将来変更されてもEQU命令の箇所だけ直して再アセンブルすれば、参照している各々の命令を直す必要がありません。

COPY

COPY命令はソース・プログラム中に他のメンバーの内容を取り込みます。必ず使うデータ定義、レジスターのEQU定義、サブルーチンなど使い方は自由です。よく使う一連の手続きなどは、同じコーディングを繰り返す代わりに別メンバーで登録しておけば、修正が必要になった時などの作業量を減らせます。

TITLE、PRINT、SPACE及びEJECT

いずれもアセンブリー・リストの出力制御に使われる命令です。TITLE命令は、リストの各ページの先頭につける見出しを設定します。”で囲まれた任意の文字列を見出しとして指定できます。
PRINT命令は、印刷内容を指定します。パラメーターにはON|OFF(PRINT命令以降のリストを印刷する|しない)GEN|NOGEN(マクロ命令内の各CPU命令などを印刷する|しない)、DATA|NODATA(8バイトを超える定数データの内容を全部印刷する|しない)があります。プログラムであればOFFはもちろん、NOGEN指定のアセンブリー・リストはデバッグの役に立ちません。特別な理由がない限りOFFとNOGENは指定しません。DATAは定数の定義が主体となるようなモジュールで使われることがあります。通常であれば、PRINT命令は指定せずにデフォルトのままでかまわないでしょう。

SPACE命令は、アセンブリー・リスト中に1行以上の空白行を挿入します。SPACE 2とすれば2行の空白行が入ります。パラメーターを省略すれば1行の空白行が入ります。EJECT命令は、リストの改ページを行います。SPACEおよびEJECT命令自身は印刷されません。
いずれの命令もアセンブリー・リストを見やすくするために使います。ソース・プログラム上では意味はありませんが、アセンブラー言語ではアセンブリー・リストはデバッグ作業の友ですから、見やすいリストは作業効率の向上に繋がります。

簡単なアセンブラー・プログラムの例

消費税を計算する簡単なプログラムです。どのアセンブラー命令が出てきたか復習してみましょう。計算結果として税込み価格をプログラムの完了コードとして出力します。(OSの仕様で4095円を超えると完了コードでは正しく表示できません)