分岐とループは、実用的なプログラムを作る上での基本でもあります。関連する機能でもある比較と併せてS/370アーキテクチャーにおける、分岐・ループ、サブルーチンの呼び出しなどについて解説します。基本の分岐命令(BC命令)については「四則演算の基本と条件分岐」を見て下さい。
整数比較命令(Compare)
1 2 3 4 5 6 |
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- CR r1,r2 レジスター : レジスター C r1,d2(x2,b2) レジスター : メモリー(フルワード) CH r1,d2(x2,b2) レジスター : メモリー(ハーフワード) CLR r1,r2 レジスター : レジスター CL r1,d2(x2,b2) レジスター : メモリー(フルワード) |
CR、C、CH
符号付き整数の比較を行います。CR命令はレジスター間での比較で、r1およびr2で示されるレジスターの内容を比較します。C命令はレジスターとメモリー間での比較で、r1で示されるレジスターの内容と第2オペランドで示される主記憶のフルワードの内容を比較します。CH命令では、r1で示されるレジスターの内容と第2オペランドで示される主記憶のハーフワードの内容が比較されます。いずれの命令も比較の結果は条件コードで通知されます。
1 2 3 4 5 6 7 8 9 10 11 |
----+----1----+----2----+----3----+----4----+----5----+----6----+ CR R0,R1 GR0 : GR1 BL xxxxxxxx IF LOW... : C R0,FWORD GR0 : FWORD BNE xxxxxxxx IF NOT EQUAL... : CH R0,=H'123' GR0 : H'123' BNH xxxxxxxx IF LOW OR EQUAL... : FWORD DC F'100' |
比較の結果、PSWに条件コードがセットされます。
1 … 第1オペランドが小さい
2 … 第1オペランドが大きい
3 … 使用されない
CLR、CL
32ビット2進数(符号無し整数)の比較を行います。先頭ビットを符号と見なさず32ビットがそのまま論理的に比較されます。
CLR命令ではr1およびr2で示されるレジスターの内容が比較され、C命令ではr1で示されるレジスターの内容と第2オペランドで示される主記憶のフルワードの内容が比較されます。いずれの命令も比較の結果は条件コードで通知されます。
1 2 3 4 5 6 7 8 |
----+----1----+----2----+----3----+----4----+----5----+----6----+ CLR R0,R1 GR0 : GR1 BL xxxxxxxx IF LOW... : CL R0,UWORD GR0 : UWORD BNE xxxxxxxx IF NOT EQUAL... : UWORD DC A(X'FEDCBA98') |
比較の結果、PSWに条件コードがセットされます。
1 … 第1オペランドが小さい
2 … 第1オペランドが大きい
3 … 使用されない
文字(文字列)比較命令(Compare Character)
1 2 3 4 |
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- CLI d1(b1),i2 即値 : メモリー(バイト) CLC d1(l,b1),d2(b2) メモリー : メモリー(バイト) CLM r1,m3,d2(b2) レジスター : メモリー(バイト) |
CLI
CLI(Compare Logical Immediate)命令は、第2オペランドで指定された1バイトの即値(固定値)と第1オペランドで指定された主記憶の1バイトを比較します。結果は条件コードで通知されます。
1 2 3 4 |
----+----1----+----2----+----3----+----4----+----5----+----6----+ CLI CHAR,C'A' COMPARE C'A' WITH CHAR AREA CLI CHAR,0 COMPARE X'00' WITH CHAR AREA CLI CHAR,X'FF' COMPARE X'FF' WITH CHAR AREA |
比較の結果、PSWに条件コードがセットされます。
1 … 第1オペランドが小さい
2 … 第1オペランドが大きい
3 … 使用されない
CLC
CLC(Compare Logical Character)命令は、第2オペランドで指定された主記憶の内容と第1オペランドで指定された主記憶の内容を比較します。比較される長さは最大256バイトで、第1オペランド側で指定します。文字の比較は左から右に1バイトずつ順に行われ、指定した長さ分の比較が終了すると命令は完了します。
1 2 3 4 5 6 |
----+----1----+----2----+----3----+----4----+----5----+----6----+ CLI AREA,C' ' COMPARE AREA WITH C' ' CLC AREA2(100),AREA COMPARE AREA2 WITH AREA : AREA DC XL256'00' AREA2 DC XL256'00' |
条件コードはCLI命令と同じです。
CLM
CLM(Compare Logical Characters under Mask)命令は、第2オペランドで指定された主記憶の連続したnバイトと第1オペランドで指定されたレジスターのm3で示されるマスクビットに対応したバイトを比較します。マスクビットは4ビットで、それぞれのビットはレジスターの各バイトに対応します。例えば、マスクビットがb’1001’であればレジスターの第1及び第4バイト(いちばん左端と右端)で、b’0011’であればレジスターの第3及び第4バイト(下位2バイト)で比較します。ICMとSTCM命令の比較版です。
1 2 3 4 |
----+----1----+----2----+----3----+----4----+----5----+----6----+ CLM R1,B'8000',AREA COMPARE AREA WITH HIGH ORDER BYTE : AREA DC CL256' ' |
CLM命令では条件コードがセットされます。
1 … 選択された第1オペランド・バイトが小さい
2 … 選択された第1オペランド・バイトが大きい
3 … 使用されない
これらの他に、MVCL命令の比較版であるCLCL命令もあります。使用する機会は少ないと考えるので講座では解説しません。必要ならば命令リファレンスマニュアルを参照して下さい。
ループ制御に使う分岐命令
1 2 3 4 5 |
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- BCTR r1,r2 レジスター → 分岐先アドレス BCT r1,d2(x2,b2) メモリー → 分岐先アドレス BXLE r1,r3,d2(b2) メモリー → 分岐先アドレス BXH r1,r3,d2(b2) (r1とr3はカウンターレジスター) |
BCTR、BCT
BCTR、BCT(Branch on Count)命令は、回数が決まっているあるいは決められるループの制御を行います。第1オペランドのレジスターはループする回数を示します。命令が実行されると第1オペランドで指定されたレジスターの内容が1だけ減算されます。減算後にレジスターの値がテストされ、0でなければ第2オペランドで示される主記憶のアドレスに分岐します。
なお、BCTR命令では第2オペランドのr2レジスター番号が0の時、常に分岐は行われず第1オペランドのレジスター内容が1だけ減算されます(*1)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
----+----1----+----2----+----3----+----4----+----5----+----6----+ LA R1,WORKAREA LOAD BEGIN OF WORKAREA LA R0,10 SET LOOP COUNTER LOOP DS 0H CLI 0(R1),C' ' END OF STRING ? BE NEXT YES, : BCT R0,LOOP DO LOOP FOR NEXT BYTE SPACE , NEXT DS 0H MVI 0(R1),X'FF' INDICATE ... : : LTR R0,R0 TEST LOOP COUNTER BNP ESCAPE IF NOT PLUS, : : BCTR R0,R8 DO LOOP UNTIL PROCESSING DONE : |
BCT、BCTR命令では、r1レジスターが0になっているとそこから更に減算され-1になってしまいます。ループの入口で0になってしまっていると最初のBCT、BCTR命令で-1になってしまい、その後延々とループが続く永久ループ状態に入ってしまいます(次に0になるまで約42億回掛かる)。サンプルのように入口で固定のループ回数をセットするような場合はともかく、ループ回数が可変になるロジックでは必ずカウンターに使うレジスターが0になっていないことを確認する癖をつけましょう。もし0ならばループに入らず脱出する、ロジックエラーにする等のセーフロジックを実装します。実際のプログラムでもこの種のバグは割と多いものです。
*1 この特性によって、BCTR命令を1減算する引き算命令の代わりに使うことも多い。
BXLE、BXH
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 31 32 33 34 |
----+----1----+----2----+----3----+----4----+----5----+----6----+ LIMIT DC F'100' NUM OF MAXIMUM LOOP ① 一般的なループ制御例(100回のループ) SLR R2,R2 CLEAR WORK COUNTER LOOP DS 0H LA R2,1(,R2) INCREMENT WORK COUNTER : : AH R0,=H'1' INCREMENT COUNTER C R0,LIMIT LIMIT ? BNE LOOP NO, LOOP AGAIN ② BXLE命令(カウンター加算)による同じ回数のループ制御例 LA R15,1 GR15 -> LOOP COUNTER LA R0,1 GR0 --> INCREMENT VALUE L R1,LIMIT GR1 --> LIMIT VALUE SLR R2,R2 CLEAR WORK COUNTER LOOP DS 0H LA R2,1(,R2) INCREMENT WORK COUNTER : : BXLE R15,R0,LOOP LOOP UNTIL LIMIT ③ BXH命令(カウンター減算)による同じ回数のループ制御例 L R15,LIMIT GR15 -> LOOP COUNTER LH R0,=H'-1' GR0 --> DECREMENT VALUE LA R1,0 GR1 --> LIMIT VALUE SLR R2,R2 CLEAR WORK COUNTER LOOP DS 0H LA R2,1(,R2) INCREMENT WORK COUNTER : : BXH R15,R0,LOOP NO, LOOP AGAIN |
BXLE(Branch on Index Low or Equal)とBXH(Branch on Index High)は、ループカウンターの加減算と限界値のテストを同時に行うループの制御命令です。レジスターを3つも使う等命令としては複雑で慣れないとわかりにくいのですが、サンプルを見てわかるようにループの中で脱出テストに費やす命令が少なくて済むので(一般例では3命令使っているので100回のループで300ステップ、BXLE/BXHでは1命令なので同じループ回数なら100ステップと1/3で済む)、ループを多用するプログラムでは実行に費やすCPU時間を減らせることになります。
第1オペランドr1レジスターは増分が加算されテストされるレジスター、第2オペランドは分岐先アドレス、第3オペランドr3で指定されるレジスターは増分値、r3+1レジスターは限界値として使われます。r3およびr3+1レジスターの内容は変わりません(r1レジスターとしても重複使用した場合を除く)。命令が実行されるとr1レジスターにr3レジスターの内容が加算され、r3+1レジスターの内容と比較されます。BXLE命令の場合、r1レジスター ≦ r3+1レジスターなら第2オペランドで示されたアドレスへ分岐します。BXH命令の場合、r1レジスター > r3+1レジスターなら第2オペランドで示されたアドレスへ分岐します。r3レジスターが奇数番号の場合、そのレジスターは増分値と限界値を兼用します。
BXLEとBXH命令は、S/370アーキテクチャーでループの制御を容易にする目的で用意されています。少し複雑ですが最初の初期設定さえしてしまえばループ内のダイナミック・ステップ数を少なく出来るという利点を持ちます。
サブルーチンの呼び出し
サブルーチンの呼び出しも分岐命令です。S/370[XA]命令セットでは、アドレッシング・モードによって複数の命令を使い分けられるようになっています。
1 2 3 4 5 6 |
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- BASR r1,r2 レジスター → 分岐先アドレス BAS r1,d2(x2,b2) メモリー → 分岐先アドレス BASSM r1,r2 レジスター → 分岐先アドレス BSM r1,r2 レジスター → 分岐先アドレス EX r1,d2(x2,b2) メモリー → 実行命令アドレス |
BASR、BAS(Branch and Save)
BAS、BASR命令は第2オペランドで指定されたアドレスに分岐します。この時、第1オペランドで指定したr1レジスターにはBASまたはBASR命令の直後のアドレスが格納され、ビット0は現アドレッシング・モード(24ビットなら0、31ビットなら1)を示します。
一般的には、外部サブルーチンの呼び出しにBASR命令を使い、内部サブルーチンの呼び出しにBAS命令を使います。第1オペランドのレジスターに格納される内容は、呼び出されたサブルーチンから見ると呼び出し元への復帰アドレスです。
1 2 3 4 5 6 7 8 9 10 |
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- BAS R10,SUB1 CALL INTERNAL SUBROUTINE SUB1 L R15,=V(EXTSUB1) LOAD EXTERNAL SUBROUTINE ENTRY BASR R14,R15 CALL IT : : SUB1 DS 0H : : BR R10 RETURN TO MAINLINE |
r1オペランドへの命令アドレスの格納とは、現PSWのアドレス部(下位32ビット)をレジスターに格納することです。また、CPU動作としては新しい分岐アドレスが決定されてからr1レジスターが置き換わります。そのため、BASR R14,R14 のように第1と第2を同じレジスターにしても正しく動作します。ただし、リンケージ規約に沿っていないため一般のプログラムでは使わないようにします。
BASR命令ではr2レジスター番号に0を指定すると分岐は行われません。ただし、r1レジスターには次の命令アドレスが格納されます。これを応用して入口点で自分のベースアドレスを設定するために使われることもあります。
BAS[R]命令は、基本的に同じアドレッシング・モードのプログラム間での呼び出しに用いられます。BAS[R]命令はS/370XAアーキテクチャーで追加された命令で、それ以前はBALとBALR(Branch and Link)命令が使われました。現在でもBAL[R]命令は有効です。多くのプログラムや以前からのアセンブラー・プログラマーは今でもBAL[R]命令を使っています。しかし、これから新たにアセンブラー言語でプログラミングを始める方は旧プログラムとの互換を取るような場合を除き、基本的にはBAS、BASR命令を使うことを勧めます(*2)。
1 2 3 4 5 6 7 8 9 10 |
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- BAL R10,SUB1 CALL INTERNAL SUBROUTINE SUB1 L R15,=V(EXTSUB1) LOAD EXTERNAL SUBROUTINE ENTRY BALR R14,R15 CALL IT : : SUB1 DS 0H : : BR R10 RETURN TO MAINLINE |
プログラムが31ビットモードで実行中であれば、BAS[R]とBAL[R]命令の動きに違いはありません。ただし、24ビットモードでのBAL[R]命令では特別な動作が追加されます。r1レジスターにセットされる復帰アドレスは下位3バイト(24ビットだから)に格納されますが、上位バイトには ILC + CC + プログラムマスクが追加してセットされます。
1 2 3 4 |
+-----+-----+-----------+-----------------------+ | ILC | CC | PGM-MASK | Instruction address | +-----+-----+-----------+-----------------------+ 0 2 4 7 8 31 |
このフラグバイトが設定されるのは、S/360アーキテクチャーとの互換のためです。BAL、BALR命令は元々単にPSWの下位32ビットをそのままr1レジスターに格納していました。実は上図のフォーマットはS/360のPSW形式と同じです。S/360からS/370へ変わった際にPSWの形式が変更され、下位32ビットのアドレス部の先頭バイトはx00固定値に変更されました。そのため、BALとBALR命令の動作結果(r1レジスターの格納内容)に非互換が生じるため、CPUがr1レジスターの先頭バイトに従来のS/360同様にILC+CC+PGMマスクを設定しているのです。
ILCは実行した命令の長さを示すコード、CCは条件コード、プログラムマスクはプログラム割込みの制御に使われるマスクビットです。24ビットモードではアドレスは下位24ビットが意味を持つので、このような追加の情報を上位バイトにセットしていたものです。一般のプログラムではこれらの制御情報をプログラム自身で扱う必要はありませんから、新たなプログラムはBAS、BASR命令を使えばいいのです。よくわからなければ、BALとBALRはBASとBASRの昔の書き方であると考えてもいいです。実際に24と31の両アドレッシング・モードを切り替えながら動くようなプログラムでは、BAL命令は使う場所を誤るとS0C4 ABENDを引き起こしたりします。そのため、お手本などにはBAL/BALRと載っていても自分が書くときにはBAS/BASRに置換えて書く癖をつけるといいでしょう。ただし、既に作られている業務アプリケーションなどの場合は問題が生じていない限り直す必要はありません。
*2 MVS3.8のような拡張アーキテクチャーが使えないシステムではBAL/BALR命令を使わねばならない。
BASSM、BSM(Branch and Save and Set Mode、Branch and Set Mode)
BASとBASR命令は同じアドレッシング・モード間のプログラム・リンケージに使う命令です。異なるアドレッシング・モード間のプログラム・リンケージには、BASSMとBSM命令を使用します。
BASSM命令は、第2オペランド(r2レジスターのみで主記憶アドレスをラベルでは指定できない)で指定されたアドレスに分岐します。第1オペランドで指定したr1レジスターにはBASSM命令の直後のアドレスが格納され、ビット0は現アドレッシング・モードを示します。分岐する際、r2レジスターのビット0に従ってアドレッシング・モードを切り替えます。ビット0が1なら31ビットモードに、ビット0が0なら24ビットモードに切り替わります。BASR命令の動作に、分岐時に分岐先アドレスのアドレッシング・モードに切り替えるという動作が加わります。
BSM命令は、アドレッシング・モード・ビットの扱いや分岐時にアドレッシング・モードを切り替える点ではBASSM命令と同じです。ただし、r1レジスターには復帰アドレスは格納されません。現アドレッシング・モード・ビットのみがビット0にセットされ、ビット1から31の内容は変更されません。そのため、アドレッシング・モード切替を伴う無条件分岐命令として使用できます。BASSM命令で呼び出されたプログラムは、呼び出し元へ戻るためにBSM命令を使います。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- L R15,ASUB1 LOAD EXTERNAL SUBROUTINE ENTRY + BIT0 INDICATES NEW ADDRESS MODE BASSM R14,R15 CALL IT WITH TARGET AMODE : : ASUB1 DC A(EXTSUB1) EXTSUB1 CSECT , : : BSM 0,R14 RETURN TO CALLER WITH CALLER AMODE |
BASR命令同様にr2レジスター番号に0を指定すると分岐は行われず、アドレッシング・モードの切り替えも行われません。BSM命令では、r1レジスターに0を指定するとGR0の内容は一切変更されません。ビット0にアドレッシング・モード・ビットがセットされることもありません。
アドレッシング・モードの扱いについては機会を改めて解説します。まずは、サブルーチンの呼び出しにはBAS、BASR命令を使うことを知ってください。また、特別な指定をせずに普通に作ったプログラムは16MB迄の仮想記憶にアクセスできる24ビット・アドレッシング・モードであるということを知っておいて下さい。
EX(Execute)
EX命令は離れた所にある命令を実行する命令?として知られています。1命令だけのサブルーチンを実行する命令と言ってもいいでしょう。実際は、命令の第2バイト(ビット8から15)の値を変更して実行するための命令です。
第2オペランドで指定されたアドレスにある命令を実行します。1命令だけ実行したらEX命令の次の命令に移ります。第1オペランドは実行する命令コードの1部を修正するために使われます。r1で指定されたレジスターの最下位バイト(ビット24から31)の内容と対象命令コードのビット8から15の論理和(OR)が取られ、命令コードのビット8から15はその論理和による値が使われます。ただし、主記憶上の命令コード自身は変更されません。なお、r1レジスターに0を指定した場合は命令コードの論理和による修飾は行われません。書かれた命令をそのまま実行する時は第1オペランドに0と書きます。
1 2 3 4 5 6 7 8 9 10 |
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- LH R15,MOVLNG LOAD MOVE LENGTH BCTR R15,0 -1 (CORRECT BY S/370 LENGTH FORMAT) EX R15,MOVE MOVE DATA(VARIABLE LENGTH) : : MOVLNG DC H'0' MOVE LENGTH MOVE MVC WORK(0),SOURCE MVC INSTRUCTION MODEL SOURCE DC CL256' ' SOURCE DATA WORK DC CL256' ' WORK DATA |
MVCやCLC命令では転送や比較されるデータの長さは命令コード内に固定されています。しかし、処理したいデータの長さが実行の都度変わる可変長になることは実際のプログラミングではよくあります。このような時、可変長処理するためにわざわざMVCLやCLCL命令を使うことはありません。256バイト以下のデータならEX命令を使えば必要な長さを可変長扱いすることができます。MVCとCLC命令の長さに関してのポイントは、実際の長さから-1することです。100なら99、1なら0、256なら255をEX命令のレジスターにセットします。UNPK命令などでも使うことができますが、長さフィールドの形式は異なるのでそれぞれの命令コードに合わせてr1レジスターの値をセットします。そのため、EX命令を使うためには命令がどのようなバイナリーコードに翻訳されるかがわかっていないといけません。