プログラムの書式
アセンブラー・プログラムは1行が80バイトです。LRECL=80の固定長レコードのデータセットを作成してそこへ書いていきます。区分データセットのメンバーとして登録するのが普通ですが、ちょっとしたプログラムや命令の動きだけを確認したいような時は、アセンブルJCL内にSYSINデータとして直接コーディングしてもいいでしょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- label op [operand,operand,…] comment X CHECK01 DS 0H L R1,AREA1 LOAD INPUT VALUE LTR R1,R1 TEST VALUE BNP ERROR1 IF NOT PLUS, DO ERROR PROC-1 * GETMAIN RC, OBTAIN WORKING STORAGE + LV=(1),LOC=ANY LTR R15,R15 SUCCEEDED ? BNZ ERROR2 NO, DO ERROR PROC-2 : : |
アセンブラーではラベル(名前)は第1桁目、命令はラベルがあればラベルの後ろに1文字以上の空白を置いてから、ラベルがなければ第2桁目以降から書けます。オペランド(命令のパラメーター)は命令の後ろに1文字以上の空白を置いてから書けます。オペランド(オペランドがない命令では命令)の後ろに1文字以上の空白を置けば、以降行末までコメントも書けます。オペランドが1行に書ききれない時は、72桁目に空白以外の文字を置き、続きのパラメーターを次の行の16桁目から(コメントは17桁目以降から)継続できます。
アセンブラー言語では最低限のルールを守れば基本的に自由コーディングできます。しかし、行によって命令の開始桁が違っていたり、人によってスタイルが変わると見にくいので伝統的な決まり事があります。なお、73桁目以降はSEQ番号ですが使わなくてもかまいません。
1~8桁目はラベル。命令は10桁目からオペランドは16桁目から書きます。MVSアセンブラー・サービスのマクロなど、命令が6文字以上ある場合はパラメーター(オペランド)開始位置もそれに合わせてずらします。その場合でも継続行は必ず16桁目からでなければなりません。現在の高水準アセンブラーではロングネームも使えるので必ずしもこのルールに沿えませんが、伝統的な基本として知っておいてください。
コメントにはいろいろスタイルがありますが、第41桁目、40桁目、36桁目などから書かれることが多いでしょう。行全体をコメントにするなら1桁目に*を置きます。オペランドがない命令の文にコメントを書く場合は命令とコメントの間にカンマ記号を置く癖をつけるといいでしょう。
1 2 |
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- IPK , SAVE CURRENT PSW KEY... |
1 2 3 4 5 6 |
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- *********************************************************************** * XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX * * * * * *********************************************************************** |
アセンブラー言語ではコメントはとても重要です。命令文ごとにコメントを付けるのが良いのですが、単なる命令自体の解説のようなコメントばかり続くと、くどくなるので注意します。何をしてるのかや、何のためにその命令を書いているのか等を残すように心がけるといいでしょう。上記のサンプルのように*記号などでコメント欄をカプセルのように囲むこともよく行われます。きちんとしたコメントは十分なドキュメントにもなります。商用プログラムではコメント・カプセルの中にドキュメント自体を書いてしまうことも少なくありません。
形を繕うための外部ドキュメントにさしたる意味はありませんが、プログラム内のきちんとしたコメントはプログラムの保守を行う上でとても役に立ちます。商用プログラムの場合、どんなに素晴らしいものでもコメントがまったく無いようなプログラムは0点と言っても過言ではありません。アセンブラー言語でプログラミングを行う場合は、学習レベルのプログラムを書くときからコメントを付ける癖をつけることを勧めます。
プログラムはCSECTで始まりENDで終わる
1 2 3 4 5 6 7 8 9 10 11 12 |
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- PROG1 CSECT , USING *,12 DEFINE BASE REGISTER STM 14,12,12(13) SAVE CALLER REGISTERS LA 12,0(,15) GR12 -> OUR 1ST BASE ADDRESS LR 15,13 SAVE CALLER SAVEAREA : : WORKAREA DC D'0' DOUBLE WORD FOR WORK DATA01A DC F'123' DEFAULT VALUE DROP , FORGET BASE REGISTER END |
プログラムの始まりはCSECT命令で示します。CSECT命令の他にもSTART命令というのもあり、言語の機能では最初のセクション開始がSTART、2番目以降のセクション開始がCSECTとされますが、特別な理由がない限りSTARTを使う必要はありません。最初であろうが2番目以降であろうが、セクションはCSECTで始めると覚えていいです。
セクションとはプログラムを構成する要素のことで、実行される命令やデータが展開される制御セクション(Control Section)、外部領域内をフィールドに分割して名前で参照できるようにする見かけセクション(Dummy Section)などがあります。CSECTは制御セクションのことで、実際に命令やデータが展開されてオブジェクト・モジュールとして出力される部分です。
プログラムの終了はEND命令で示します。END命令にはプログラムの実行開始位置を指定することもできます。省略すればCSECTの先頭から実行されます。実行開始位置をセクションの途中からにする手法はさほど難しいものではありませんが、慣れるまではわかりにくいので、例外を除きプログラムはセクションの先頭から実行されるものなのだと覚えていいでしょう。
ロケーション・カウンター
1 2 3 4 5 6 7 8 |
LOC OBJECT CODE ADDR1 ADDR2 000000 90EC D00C 0000C 6 BEGIN STM 14,12,12(13) 000004 41C0 F000 00000 7 LA 12,0(,15) 000008 18FD 8 LR 15,13 : 000118 00000064 45 DATA DC F'100' LOCと表示されている箇所がロケーション・カウンターの値を示す |
ロケーション・カウンターは、オブジェクト・モジュール内の命令やデータが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を、文字なら空白を与えます。
1 2 |
000118 00000064 45 A DC F'100' 00011C 46 B DS F |
具体例で示します。DC命令では領域内の値がアセンブリー・リストに出てきます。DS命令ではアドレスしか出ません。DC命令ならデータ域の構造がリストを見れば直感で感じ取れます。DS命令だとデータ型を見ていちいち考えなければなりません。簡単なプログラムではメリットが感じにくいですが、プログラム内のデータ・フィールドが増えてきたり、データ構造が複雑になってくると、このようなこともデバッグを楽にする方法の1つになるのです。
DC命令では、オペランドで定数のデータ型、長さ、複写回数、初期値を設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
DC [複写因数]型[長さ]'定数値' ----+----1----+----2----+----3----+----4----+----5----+----6 LABEL1 DS 0H IC R1,CHAR1 LOAD 1BYTE INTO LOW : CHAR1 DC C'A' A DC CL10'ABCDEFGH' 'ABCDEFGH ' DC 3CL2'XY' XYXYXY DC X'00' x00 DC XL4'123' x00000123 DC F'12345' x00003039 DC FL3'-875' xFFFC95 DC H'3000' x0BB8 DC D'0' x0000000000000000 DC P'12345' x12345C DC P'-875' x875D DC PL3'-875' x00875D DC A(CHAR1) x00000108 DC V(SUBRTN1) x00000000 |
複写因数は、同じ定数を繰り返し定義する時に指定します。繰り返された定数は連続した領域として定義されます。型は定数の種類を示し、文字型、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 | 外部モジュールの入口点アドレスを定義する。 | 外部サブルーチンの呼び出しで利用する。 |
ORG命令は、ロケーション・カウンターを変更する命令です。主にデータ領域の再定義(Re-define)を行うために使用されます。
1 2 3 4 5 6 7 |
----+----1----+----2----+----3----+----4----+----5----+----6 EXECPARM DS 0H DC XL102'00' EXECUTE PARMS ORG EXECPARM LENGTH DC H'0' LENGTH OF DATA DATA DC CL100' ' PARM STRING ORG , |
最初のORG命令でロケーションカウンターをEXECPARMの先頭に戻し、102バイトフィールドを、2バイトのLENGTHと100バイトのDATA領域として再定義しています。その後に、ロケーション・カウンターを元にに戻しています。ORG命令をオペランドなしで記述すれば、ロケーション・カウンターはそれまでの最高値に再設定されます。これは、ロケーション・カウンターを元に戻すことと同じです。COBOLなどではデータ領域のRE-DEFINEはよく使われますが、アセンブラーでも簡単にできます。
リテラル定数とLTORG
リテラル定数は、DC命令を使わずに命令のオペランド内でダイレクトに定数を指定するために用いられます。DCの代わりに=を使います。同じ内容の定数であれば重複して定義されることはなく、以前に定義されたものが再使用されます。
1 2 3 4 5 6 7 8 9 10 |
----+----1----+----2----+----3----+----4----+----5----+----6 L R1,=F'1' L R2,=H'321' : : LTORG , DATAAREA DS 0D DC CL8'GHYTRX' PACK2 DC P'1234567' END |
リテラル定数は、通常END命令によってすべての命令とデータの後ろに展開されます(この例ではPACK2の後ろ)。リテラル定数が集められ展開される場所がリテラル・プールです。LTORG命令を使えば、任意の位置にリテラル・プールを置くことができます。サイズの大きなプログラムなど、ベース・レジスターがアドレスできる範囲との関係で、プログラマーがリテラルプールの位置を調整したい場合に用いられます。
EQU
EQU命令は式や数値に名前を付けるために使われます。よく使われるのがレジスター番号の表記です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
----+----1----+----2----+----3----+----4----+----5----+----6 MH R0,=Y(TAX) : : R0 EQU 0 R1 EQU 1 R2 EQU 2 : R14 EQU 14 R15 EQU 15 RA EQU 10 RB EQU 11 TAX EQU 5 END |
EQU命令で数値に名前を付けて定義し、命令ではその名前で値を参照するようにすれば、計算に使う数値が将来変更されてもEQU命令の箇所だけ直して再アセンブルすれば、参照している各々の命令を直す必要がありません。
COPY
COPY命令はソース・プログラム中に他のメンバーの内容を取り込みます。必ず使うデータ定義、レジスターのEQU定義、サブルーチンなど使い方は自由です。よく使う一連の手続きなどは、同じコーディングを繰り返す代わりに別メンバーで登録しておけば、修正が必要になった時などの作業量を減らせます。
1 2 3 4 5 6 7 8 9 10 |
----+----1----+----2----+----3----+----4----+----5----+----6 L R1,=F'1' : DATAAREA DS 0D DC CL8'GHYTRX' PACK2 DC P'1234567' COPY REGEQU INCLUDE GPRS EQUATES END |
TITLE、PRINT、SPACE及びEJECT
いずれもアセンブリー・リストの出力制御に使われる命令です。TITLE命令は、リストの各ページの先頭につける見出しを設定します。”で囲まれた任意の文字列を見出しとして指定できます。
PRINT命令は、印刷内容を指定します。パラメーターにはON|OFF(PRINT命令以降のリストを印刷する|しない)、GEN|NOGEN(マクロ命令内の各CPU命令などを印刷する|しない)、DATA|NODATA(8バイトを超える定数データの内容を全部印刷する|しない)があります。プログラムであればOFFはもちろん、NOGEN指定のアセンブリー・リストはデバッグの役に立ちません。特別な理由がない限りOFFとNOGENは指定しません。DATAは定数の定義が主体となるようなモジュールで使われることがあります。通常であれば、PRINT命令は指定せずにデフォルトのままでかまわないでしょう。
1 2 3 4 5 6 7 8 |
000094 C1C2C3C4C5C6C7C8 53 LONGDATA DC C'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' 52 PRINT DATA 000094 C1C2C3C4C5C6C7C8 53 LONGDATA DC C'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' 00009C C9D1D2D3D4D5D6D7 0000A4 D8D9E2E3E4E5E6E7 0000AC E8E9F0F1F2F3F4F5 0000B4 F6F7F8F9 |
SPACE命令は、アセンブリー・リスト中に1行以上の空白行を挿入します。SPACE 2とすれば2行の空白行が入ります。パラメーターを省略すれば1行の空白行が入ります。EJECT命令は、リストの改ページを行います。SPACEおよびEJECT命令自身は印刷されません。
いずれの命令もアセンブリー・リストを見やすくするために使います。ソース・プログラム上では意味はありませんが、アセンブラー言語ではアセンブリー・リストはデバッグ作業の友ですから、見やすいリストは作業効率の向上に繋がります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
----+----1----+----2----+----3----+----4----+----5----+----6 PRINT ON,GEN,NODATA TITLE 'MY ASSEMBLER PROGRAM' : : L R1,=F'1' L R2,=F'1' SPACE 2 ST R1,AREA1 MH R2,=H'8' EJECT , DATAAREA DS 0D : : END |
簡単なアセンブラー・プログラムの例
消費税を計算する簡単なプログラムです。どのアセンブラー命令が出てきたか復習してみましょう。計算結果として税込み価格をプログラムの完了コードとして出力します。(OSの仕様で4095円を超えると完了コードでは正しく表示できません)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- TITLE 'EASY TAX CALCULATOR' MYPROG CSECT , USING *,R12 DEFINE BASE REGISTER LR R12,R15 SET OUT BASE ADDRESS SPACE , L R1,PRICE LOAD PRICE VALUE MH R1,=Y(RATE) CALCULATE TAX SLR R0,R0 CLEAR WORKREG D R0,=F'100' CONVERT TO PERCENT ST R1,TAX SAVE TAX AMOUNT SPACE , L R15,PRICE SET COMPLETION CODE = A R15,TAX PRICE + TAX SVC 3 RETURN TO OS * PRICE DC F'1000' PRICE TAX DC F'0' TAX RATE EQU 5 TAX RATE LTORG , EJECT , COPY REGEQU INCLUDE GPRS EQUATES END [REGEQU] ----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- R0 EQU 0 R1 EQU 1 R12 EQU 12 R15 EQU 15 |