08.比較と分岐・ループ、サブルーチンの呼び出し
分岐とループは実用的なプログラムを作る上での基本でもあります。関連する機能でもある比較と併せてS/370アーキテクチャーにおける、分岐・ループ、サブルーチンの呼び出しなどについて解説します。基本の分岐命令(BC命令)については「四則演算の基本と条件分岐」を見て下さい。
整数比較命令(Compare)
----+----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----+ 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に条件コードがセットされます。
0 … 両オペランドは等しい
1 … 第1オペランドが小さい
2 … 第1オペランドが大きい
3 … 使用されない
CLR,CL
32ビット2進数(符号なし整数)の比較を行います。先頭ビットを符号と見なさず32ビットがそのまま論理的に比較されます。
CLR命令はレジスター間での比較です。r1およびr2で示されるレジスターの内容が比較されます。C命令はレジスターとメモリー間での比較です。r1で示されるレジスターの内容と第2オペランドで示される主記憶のフルワードの内容が比較されます。いずれの命令も比較の結果は条件コードで通知されます。
----+----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に条件コードがセットされます。
0 … 両オペランドは等しい
1 … 第1オペランドが小さい
2 … 第1オペランドが大きい
3 … 使用されない
文字(文字列)比較命令(Compare Character)
----+----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----+----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に条件コードがセットされます。
0 … 両オペランドは等しい
1 … 第1オペランドが小さい
2 … 第1オペランドが大きい
3 … 使用されない
CLC
CLC(Compare Logical Character)命令は第2オペランドで指定された主記憶の内容と、第1オペランドで指定された主記憶の内容が比較されます。比較される長さは最大256バイトで、第1オペランド側で指定します。文字の比較は左から右に1バイトずつ順に行われ、指定した長さ分の比較が終了すると命令は完了します。
----+----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----+----5----+----6----+ CLM R1,B'8000',AREA COMPARE AREA WITH HIGH ORDER BYTE : AREA DC CL256' '
-
CLM命令は条件コードがセットされます。
0 … 選択されたバイトの全てが等しい、またはマスクビットが全て0
1 … 選択された第1オペランド・バイトが小さい
2 … 選択された第1オペランド・バイトが大きい
3 … 使用されない
これらの他にMVCL命令の比較版であるCLCL命令もあります。使用する機会は少ないと考えるので講座では解説しません。必要ならば命令リファレンスマニュアルを参照して下さい。
ループ制御に使う分岐命令
----+----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----+----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になってしまい、その後延々とLOOPが続く永久ループ状態に入ってしまいます(次に0になるまで約42億回掛かる)。サンプルのように入口で固定のループ回数をセットするような場合はともかく、ループ回数が可変になるロジックでは必ずカウンターに使うレジスターが0になっていないことを確認する癖をつけましょう。もし0ならばループに入らず脱出する、ロジックエラーにする、等のセーフロジックを実装します。実際のプログラムでもこの種のバグは割と多いものです。
BXLE,BXH
----+----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----+----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-- 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命令を使うことを勧めます。(MVS3.8のような拡張アーキテクチャーが使えないシステムではBAL/BALR命令でなければなりません)
----+----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 + プログラムマスクが追加してセットされます。
+-----+-----+-----------+-----------------------+ | 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に置換えて書く癖をつけるといいでしょう。
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-- 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-- 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命令を使うためには命令がどのようなバイナリーコードに翻訳されるかがわかっていないといけません。