ハウスキーピング(プログラム冒頭での呼び出し元レジスター内容の保管やベースアドレスの設定処理)の処理にはプログラマーによっていろいろな方法が使われますが、基本は「リンケージ規約の遵守」です。決まり切った手順でもあるので一度スタイルを決めればずっと使い続けられます。ここでは私自身が長年使ってきたコーディングのいくつかをサンプルとして紹介します。
オーソドックスなコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- PROGNAME CSECT , DEFINE CONTROL SECTION USING *,12 DEFINE BASE REGISTER STM 14,12,12(13) SAVE CALLER REGISTERS LR 12,15 GR12 -> OUR 1ST BASE ADDRESS LR 15,13 SAVE CALLER SAVEAREA CNOP 0,4 INSURE FULL WORD BOUNDARY BAS 13,*+4+72 AROUND OUR SAVEAREA DC 18F'-1' OUR GPR SAVEAREA ST 15,4(,13) SAVE CALLER SAVEAREA POINTER ST 13,8(,15) SET BACK CHAIN FOR SA TRACE : : : L 13,4(,13) LOAD CALLER SAVEAREA L 14,12(,13) LOAD RETURN ADDRESS LM 0,12,20(13) LOAD CALLER GPRS BR 14 RETURN TO CALLER |
レジスター保管域をハウスキーピングのコードの中に定義した例です。レジスター保管域のように実行中に参照することがないデータ領域にいちいち名前を付けるのは面倒なので私自身は好んで使っていました。呼び出し元への復帰時にはGR15に復帰コードが設定されているものとしています。
モジュールの先頭にヘッダーを付ける
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- PROGNAME CSECT , DEFINE CONTROL SECTION USING *,12 DEFINE BASE REGISTER B 4+30(,15) AROUND PROGRAM HEADER DC AL1(8) MODULE NAME LENGTH DC CL8'MYPROG1' MODULE NAME DC CL8'PROGNAME' MODULE ENTRY NAME DC CL8'&SYSDATC' ASSEMBLY DATE(YYYYMMDD) DC CL5'&SYSTIME' ASSEMBLY TIME(HH.MM) SPACE , STM 14,12,12(13) SAVE CALLER REGISTERS LR 12,15 GR12 -> OUR 1ST BASE ADDRESS LR 15,13 SAVE CALLER SAVEAREA CNOP 0,4 INSURE FULL WORD BOUNDARY BAS 13,*+4+72 AROUND OUR SAVEAREA DC 18F'-1' OUR GPR SAVEAREA ST 15,4(,13) SAVE CALLER SAVEAREA POINTER ST 13,8(,15) SET BACK CHAIN FOR SA TRACE |
プログラムの冒頭にヘッダーを付けることはしばしば行われます。名前、アセンブル日付と時刻などはヘッダー情報の代表的なものです。複数のプログラムを結合して1つのロードモジュールにするような場合は、各CSECTの名前も入れればダンプ・リストを見た際に識別しやすくなります。商用プログラムでは製品のバージョンやPTF用のパッチ・エリア、COPYRIGHT表示なども入れられます。
一般のプログラムなら入口点ではGR15にプログラムの先頭アドレスが格納されていますから、ヘッダーの長さだけジャンプする無条件分岐命令を先頭に置き続いてヘッダーのデータを書きます。GR15を基点にしてヘッダーの長さ+分岐命令の長さを指定してヘッダーを迂回します。ヘッダーの長さはプログラム設計時に決まり、後で変わることは基本的にないので分岐先を変位で指定していますが、名前を付けて迂回するなら下記のように書けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- PROGNAME CSECT , DEFINE CONTROL SECTION USING *,12 DEFINE BASE REGISTER B HOUSKEEP-*(,15) AROUND PROGRAM HEADER DC AL1(8) MODULE NAME LENGTH DC CL8'MYPROG1' MODULE NAME DC CL8'PROGNAME' MODULE ENTRY NAME DC CL8'&SYSDATC' ASSEMBLY DATE(YYYYMMDD) DC CL5'&SYSTIME' ASSEMBLY TIME(HH.MM) SPACE , HOUSKEEP DS 0H STM 14,12,12(13) SAVE CALLER REGISTERS LR 12,15 GR12 -> OUR 1ST BASE ADDRESS : |
ヘッダー用マクロを作る
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
MACRO &L MDHDR &NAME=,&ENTRY= LCLC &NM,&EP LCLC &D1,&D2,&D3,&T1,&T2 &D1 SETC '&SYSDATC'(1,4) &D2 SETC '&SYSDATC'(5,2) &D3 SETC '&SYSDATC'(7,2) &T1 SETC '&SYSTIME'(1,2) &T2 SETC '&SYSTIME'(4,2) &NM SETC '&SYSECT' AIF ('&NAME' EQ '').ENDNM &NM SETC '&NAME' .ENDNM ANOP , &EP SETC '&SYSECT' AIF ('&ENTRY' EQ '').ENDEP &EP SETC '&ENTRY' .ENDEP ANOP , B 4+32(,15) AROUND PROGRAM HEADER DC AL1(8) MODULE NAME LENGTH DC CL8'&NM' MODULE NAME DC CL8'&EP' MODULE ENTRY NAME DC CL10'&D1/&D2/&D3' ASSEMBLY DATE DC CL5'&T1:&T2' ASSEMBLY TIME MEND |
いちいちヘッダーを書くのが面倒、プログラムを分割して開発するがヘッダースタイルは統一したいといった場合はヘッダーを生成するマクロ命令を作ると便利です。MACROからMENDで囲まれた部分をMDHDRと言う名前のメンバーで作成します。プログラムではこのMDHDRマクロを書けばアセンブル時にマクロ内で定義したヘッダーデータが展開されます。下記にヘッダーマクロを使ったサンプルを示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
PROGNAME CSECT , DEFINE CONTROL SECTION USING *,12 DEFINE BASE REGISTER MDHDR , EXPAND MODULE HEADER STM 14,12,12(13) SAVE CALLER REGISTERS LR 12,15 GR12 -> OUR 1ST BASE ADDRESS LR 15,13 SAVE CALLER SAVEAREA CNOP 0,4 INSURE FULL WORD BOUNDARY BAS 13,*+4+72 AROUND OUR SAVEAREA DC 18F'-1' OUR GPR SAVEAREA ST 15,4(,13) SAVE CALLER SAVEAREA POINTER ST 13,8(,15) SET BACK CHAIN FOR SA TRACE PROGNAME CSECT , DEFINE CONTROL SECTION USING *,12 DEFINE BASE REGISTER MDHDR NAME=MODULE1 EXPAND MODULE HEADER STM 14,12,12(13) SAVE CALLER REGISTERS LR 12,15 GR12 -> OUR 1ST BASE ADDRESS LR 15,13 SAVE CALLER SAVEAREA CNOP 0,4 INSURE FULL WORD BOUNDARY BAS 13,*+4+72 AROUND OUR SAVEAREA DC 18F'-1' OUR GPR SAVEAREA ST 15,4(,13) SAVE CALLER SAVEAREA POINTER ST 13,8(,15) SET BACK CHAIN FOR SA TRACE |
オーソドックスなコード(再入可能プログラム)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- PROGNAME CSECT , DEFINE CONTROL SECTION USING *,12 DEFINE BASE REGISTER STM 14,12,12(13) SAVE CALLER REGISTERS LR 12,15 GR12 -> OUR 1ST BASE ADDRESS GETMAIN R,LV=72 GETMAIN OUR GPR SAVEAREA ST 13,4(,1) SAVE CALLER SAVEAREA POINTER ST 1,8(,13) SET BACK CHAIN FOR SA TRACE LR 15,13 KEEP CALLER GPR SAVEAREA LR 13,1 LOAD OUR NEW SAVEAREA LM 0,1,20(15) RELOAD ENTERED GR0-1(PARAMETER) : : : LR 1,13 GR1 --> OUR GPR SAVEAREA L 13,4(,13) RESTORE CALLER GPR SAVEAREA ST 15,16(,13) SAVE RETURN CODE FREEMAIN R,LV=72,A=(1) FREEMAIN OUR GPR SAVEAREA LM 14,12,12(13) RESTORE CALLER REGISTERS BR 14 RETURN TO CALLER |
再入可能(リエントラント構造)プログラムは、自分のプログラム内に書き込みデータを持つことができないのでレジスター保管域も定義できません。レジスター保管域をプログラムの外に確保するサンプルです。
GR13をベースレジスターに使う
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 |
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- PROGNAME CSECT , DEFINE CONTROL SECTION B 4+68+30(,15) AROUND PROGRAM HEADER DC 17F'-1' OUR REGISTER SAVEAREA DC AL1(8) MODULE NAME LENGTH DC CL8'MYPROG1' MODULE NAME DC CL8'PROGNAME' MODULE ENTRY NAME DC CL8'&SYSDATC' ASSEMBLY DATE(YYYYMMDD) DC CL5'&SYSTIME' ASSEMBLY TIME(HH.MM) SPACE , USING PROGNAME,13,12 DEFINE OUR BASE REGISTER STM 14,12,12(13) SAVE CALLER REGISTERS LR 14,13 SAVE CALLER SAVEAREA LR 13,15 GR13 --> OUR 1ST BASE ADDRESS + AND OUR SAVEAREA LA 12,2048(,13) GR12 --> OUR 2ND BASE ADDRESS LA 12,2048(,12) ST 14,4(,13) SAVE CALLER SAVEAREA POINTER ST 13,8(,14) SET BACK CHAIN FOR SA TRACE : : : L 13,4(,13) LOAD CALLER SAVEAREA ST 15,16(,13) SAVE RETURN CODE LM 14,12,12(13) LOAD RETURN ADDRESS,RETURN CODE+ AND RESTORE CALLER REGISTERS BR 14 RETURN TO CALLER |
再入可能である必要がないプログラムではレジスター保管域を示すGR13をベース・アドレスにしたプログラムを作成することもできます。サイズが大きくなるプログラムでも、GR13、12、11とベース・アドレスに使えるレジスターを1つでも多くすることができます。サンプルではレジスター保管域をプログラムの先頭に置き、GR13とGR12をベース・レジスターにしています。レジスター保管域を DC 17F と68バイトしか定義していないのは間違いではありません。先頭の無条件分岐命令自身が長さ4バイトなので、合わせて72バイトになります。保管域の最初のワードはアセンブラー言語では未使用であることを応用してものです。
筆者はバッチ処理プログラムの場合は、好んでGR13をベース・レジスター兼RSAポインターにしていました。たかが72バイト領域の為だけに汎用レジスターを1つ使ってしまうのは勿体ないです。再入可能プログラムの場合は、ローカル作業域の先頭にレジスター保管域を置き、その先頭アドレスをGR13に格納するスタイルを多用しました。再入可能プログラムではタスク等の実行単位毎にローカル作業域を必要としますが、GR13をそのベース・レジスターにすれば72バイト保管域の後ろに約4KBの作業域を展開させることができます。
一切のサブルーチンを呼ばないなら
1 2 3 4 5 6 7 8 9 10 11 |
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- PROGNAME CSECT , DEFINE CONTROL SECTION USING *,12 DEFINE BASE REGISTER STM 14,12,12(13) SAVE CALLER REGISTERS LR 12,15 GR12 -> OUR 1ST BASE ADDRESS : : : L 14,12(,13) LOAD RETURN ADDRESS LM 0,12,20(13) LOAD CALLER GPRS BR 14 RETURN TO CALLER |
あまりいいサンプルではありませんが、将来に渡っても絶対サブルーチンを呼ばないのならこのようにレジスター保管域を持たなくてもかまいません。OSのAPIは殆どがSVC呼び出しなのでレジスター保管域を必要としませんが、データセットのアクセスに使うGETやPUTマクロなど、APIによってはGR13がレジスター保管域であることを要求するものもあるので注意します。
リンケージ・スタックを使う
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- PROGNAME CSECT , DEFINE CONTROL SECTION USING *,12 DEFINE OUR BASE REGISTER BAKR 14,0 STACK ENTERED PSW AND GPRS/ARS LR 12,15 GR12 -> OUR 1ST BASE ADDRESS SPACE , GETMAIN R,LV=72 GETMAIN OUR GPR SAVEAREA LR 13,1 GR13 -> OUR NEW GPR SAVEAREA MVC 4(4,13),=CL4'F1SA' PUT ACRONYM INTO OUR SAVEAREA + BACKWARD POINTER INDICATING GPRS SAVED ON STACK : : : LR 2,15 KEEP RETURN CODE LR 1,13 GR1 --> OUR GPR SAVEAREA FREEMAIN R,LV=72,A=(1) FREEMAIN OUR GPR SAVEAREA LR 15,2 SET RETURN CODE IN GR15 PR , RESTORE ENTERED PSW(1ST WORD), + GPRS/ARS 2-14 FROM LINKAGE + STACK AND RETURN TO CALLER. + GPR/AR 15,0,1 WILL BE PASSED + WITH CURRENTLY VALUE. |
リンケージ・スタックは、ESA/390から利用できる機能です。先頭のBAKR 14,0命令は現PSWの前半ワード内容とGR14の内容で後半ワードを生成した呼び出し元への復帰用PSW、および汎用レジスターとアクセス・レジスターをスタックに保管します。最後のPR命令はスタックに保管されたPSWとレジスター2から14が復元されます。結果としてレジスターを復元して呼び出し元に戻ることになります。レジスター15、0と1は、PR命令発行時の内容がそのまま渡ります。BAKRはスタックへのPUSH、PRはスタックからのPOPです。内部サブルーチンでもレジスターの保管を気にせずに済むので使うと便利な機能です。
MVSではリンケージ・スタックの利用は必須ではありませんし、使う使わないの選択は自由なので自分が呼び出すサブルーチン(OSのAPIを含む)がリンケージ・スタックを使う保証はありません。そのため、GR13には標準のレジスター保管域をポイントする方が間違いありません。この場合、呼び出し元保管域のポインターにはアドレスの代わりに’F1SA’の4文字を設定する規約になっています。
現実的には、プログラムは最初に実行される時にOSが用意したレジスター保管域がGR13で渡されるため、GR13を他の目的で使わない限りわざわざ別に用意しなくても問題はおきないでしょう(例えば、QSAMのGET/PUTマクロの利用など)。ただし、プログラムがABENDした時のダンプには正しいSAトレースが出力されなくなります。自分は呼び出し元の保管域を使用しなくても、呼び出すプログラムのために標準通りの保管域を用意し、互いにチェインする代わりにF1SAを表示するのはレジスター保管域とリンケージスタックを混在させるためのMVSの決まり事なのです。テスト的に作成するプログラムでない限り規約通りにする方がいいでしょう。