S/370アセンブラー講座の終わりに「番外編」として実用的なヒントなどを少し。ここまでわかれば「OS/390アセンブラーハンドブック」を読んでもきっとその内容が理解できることと思います。
同ハンドブックでは、アセンブラーのプログラムは「大部分が定石ともいうべきコードの書き方で、大半が構成される」と述べています。ここ迄の記事で解説したことは、その定石コードを理解するために必要な前提知識とも言えます。「OS/390アセンブラーハンドブック」には、実際の現場でも役立ついろいろなヒントも載っていますから是非参考にしてみて下さい。
なお、実用品のプログラムを書くためには、MVS(あるいはMSP、VOS3)のアセンブラー・サービス(プログラミング・インターフェース:API)を使うことを避けて通れません。CPU命令だけの実用品プログラムは、OS自身の割り込み処理ルーチンでもない限り、我々のレベルで書くことはまずありません。MVSのアセンブラー・サービスについては、API(MVSアセンブラー・サービス)で学ぶMVSの機能(基礎編)のカテゴリーで解説しています。
レジスターに1を足す、1を引く
レジスターの値に固定値を加算、減算するには加減算命令の使用が思い浮かびます。しかし、内容によっては加減算ではない命令で行うこともできます。
1 2 3 4 5 6 |
● 1を足す LA R1,1(,R1) GR1 = GR1 + 1 ● nを足す LA R1,n(,R1) GR1 = GR1 + n (nは0から4095迄) ● 1を引く BCTR R1,0 GR1 = GR1 - 1 |
これらのテクニックは、S/370アセンブラー言語では古くから使われています。いずれも命令の動作を応用した「へぇー、そうなんだ」と言うものの代表です。覚えるとアセンブラーがちょっとわかってきた気がする小さな幸せですが、実戦では多用されているものです。なお、LA命令ではその和の最大値が24ビット・アドレッシング・モードでは最大16MB-1、31ビット・アドレッシング・モードでは最大2GB-1までになることと減算は利用できないことに注意します。他にもいくつかの「へぇー、そうなんだ」が「OS/390アセンブラーハンドブック」に載ってます。
1 2 3 4 5 6 7 8 |
● nを足す AHI R1,10000 GR1 = GR1 + 10000 ● nを引く AHI R1,-10 GR1 = GR1 - 10 ● nを足す(32Kを超える値) AFI R1,100000 GR1 = GR1 + 100,000 ● nを引く(32Kを超える値) AFI R1,-200000 GR1 = GR1 - 200,000 |
AHIは、ESA/390で追加された新しい命令です。BCTR命令では1しか引けませんが、これなら-32768から+32767の加減算ができます。従来のAH命令と違って、命令とは別のハーフワード数の定義をせずに利用できます。さらに、現在(2024年)のIBM Zでは、AFIという32bit符号付き2進数の即値を指定できる命令も追加されています。
S/370アセンブラー講座では、S/370アーキテクチャーでの基本的な(伝統的な)命令しか解説してませんが、慣れたら命令リファレンスを見て便利そうな命令には積極的にトライして見て下さい(*1)。
*1 富士通、日立でも動かすプログラムを作る場合は要注意。特に、IBMのzアーキテクチャーで追加されたような最新の命令セットはどこまで互換かは動かしてみないとわからない。
文字列転送
ESA/390では、MVSTという文字列転送を行う新しい命令も追加されています。MVST命令は、C言語におけるstrcpyです。MVC命令やMVCL命令と違い、転送する長さを指定しません。代わりに、文字列の終端文字(NULLターミネート)で転送範囲(長さ)が認識され、終端文字も含めて文字列が転送されます。比較であればCLST命令があります。
1 2 3 4 5 6 7 8 9 |
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-- LA R0,0 SET TERMINATE CHAR = NULL LA R14,DATA2 LOAD TARGET ADDRESS LA R15,DATA1 LOAD ORIGIN ADDRESS MVST R14,R15 MOVE STRING WITH NULL TERMINATE : DATA1 DC C'ABCDEFGHIJKLMNOPQRSTUVWXYZ',X'00' DATA2 DC 256X'FF' |
ESA/390以降ではUNIXシステムサービスやLinux自身をサポートするようになったため(z/Linux)、Linuxの移植を意識した?と思われる命令がいろいろと追加で実装されています。それらはすべてが公開されていないようですが、1部はアーキテクチャー解説書に載っています。MVSTもCLSTも公開された命令です。
わかりやすいロジックを心がけましょう
アセンブラー自体がわかりにくい、と言われてしまえばそれまでですが、わかりやすいプログラムがいちばんです。せっかくアセンブラーを使うのですからクールなロジックを書くのもいいのですが、業務であれば他人が見てわかるプログラムでないといけません。多少冗長なロジックであったり、メモリーをそれなりに使っていたり、CPUを少し余計に使ったとしても、普通の人が見てわかるロジックにすることが業務用のアプリケーション・プログラムでは大切です。
LA命令で加算したり、BCTR命令で1を引いたりするのは、命令の動きや機能がわかっていれば簡単に解読できるので、そういうものはいいと考えます。ただし、ロジックを解読するのに複雑な数学の知識を必要としたり、あまりにもトリッキーなコードは考え物です。書いた人自身が生涯を掛けてメンテナンスするならともかく、ほとんどの業務プログラムがそうではありません。シンプルなプログラム・コードにして保守する人の負担を減らした方がいいと個人的には考えます。いくらコメントをきちんとつけても難解なものは難解です。
このあたりはプログラマーのプライドにも関わってくるので賛否両論でしょうが、自分自身の経験から言えば、やはり「シンプル・イズ・ザ・ベスト」です。そして、この種の文句は自分の作ったプログラムに対してではなく、他人が作ったプログラムに対して生まれます。そもそも自分が書いたコードですら何年か経って見返した時、「これって何をやってたんだ?、何のために必要だったんだっけ?」「ひどいなぁ」と思うのですから、人が書いたコードなら尚更です。
実用ではたぶん必要としませんが、アセンブラーっぽい例題でひとつ例を挙げます。「ビットの数を数え上げる」です。BINARYと言うフルワードの領域に入っている内容のうち、1であるビットの数を数える処理です。(符号は考えず、32ビット中の0か1のみを判定する)
- たいていの人が考えつく方法(結果はGR15に入る)
1 2 3 4 5 6 7 8 |
L R1,BINARY LOAD 32BIT VALUE SLR R15,R15 CLEAR NUM OF BITS LA R14,32 SET LOOP COUNTER LOOP1 DS 0H SLR R0,R0 CLEAR WORKREG SLDL R0,1 MOVE NEXT BIT TO WORKREG AR R15,R0 ADD 1 OR 0 BCT R14,LOOP1 LOOP UNTIL FINAL BIT |
- 数学がわかる人が考えた方法(結果はGR2に入る)
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 |
L R2,BINARY LOAD 32BIT VALUE LR R3,R2 GR3 <-- GR2 SRL R3,1 GR3 = GR3 >> 1 N R3,=A(X'77777777') GR3 = GR3 & 0x77777777 LR R4,R2 GR4 <-- GR2 SRL R4,2 GR4 = GR4 >> 2 N R4,=A(X'33333333') GR4 = GR4 & 0x33333333 LR R5,R2 GR5 <-- GR2 SRL R5,3 GR5 = GR5 >> 3 N R5,=A(X'11111111') GR5 = GR5 & 0x11111111 SR R2,R3 GR2 = GR3 SR R2,R4 GR2 = GR4 SR R2,R5 GR2 = GR5 LR R3,R2 GR3 <-- GR2 SRL R3,4 GR3 = GR3 >> 4 AR R2,R3 GR2 = GR2 + GR3 N R2,=A(X'0F0F0F0F') GR2 = GR2 & 0x0F0F0F0F LR R3,R2 GR3 <-- GR2 SRL R3,16 GR3 = GR3 >> 16 AR R2,R3 GR2 = GR2 + GR3 LR R0,R2 GR2+3 = (GR2+2) + (GR2+3) N R0,=A(X'0000FF00') I SRL R0,8 I AR R2,R0 V N R2,=A(X'000000FF') GR2 = GR2 & 0x000000FF |
最初の方法では、ビットを左へ1つずつずらして行き、溢れたビットを足し算しています。溢れたビットが1なら+1になり、0なら0を足しても変わりません。命令の動きを知っていれば比較的容易に理解できます。
2番目の方法は、雑誌に載っていたインテルのx86アセンブラーのサンプルをS/370用に直したものです。32ビットを4ビットずつ8つのブロックに分けて、4ビット単位に1の個数を求め、その考えを32ビットに拡張するものだそうです。4ビットをabcdと4つに並べた時、この4ビットの値は「8a+4b+2c+1d」となるそうです。これから「7a+3b+1c」を引くと、その4ビット中で1になっているビットの個数が求められる、と言うものです。
この方法の土台になっている数学的知識を筆者自身がよくわかっていないので、サンプルのコメントもただの命令動作の説明でしか書けませんでした。ちなみにその雑誌の記事では上記の2通りが紹介されていて、アセンブラーならではのコードとして紹介されていたのが「8a+4b+2c+1d」から答えを求めるものでした。その理由は実行する命令数が少ないからです。1番目の方法ではソースコードの行数は少ないですが、ループするので実際に実行される命令数は131命令になります。2番目の方法ではソースコードの行数は多いですが、ループしないので実際に実行される命令数は25命令で済みます。CPUの使用量、実行に掛かる時間で見れば、当然2番目の方法に軍配が上がります。
しかし、実際の現場仕事としてのプログラムならどうでしょうか?少なくとも私にはよくわかりませんでした。今もわかってませんし突き詰めようとも思ってません。サンプル通りに書いたから動きますが、考え方だけ言われてゼロからコードを書けとなったら、きっとお手上げです。このトピックのネタにしたのも、アセンブラー講座の題材を探すためにめくってた昔の雑誌に載っていたのを見て、「これって(アセンブラーらしいプログラムだよと)言いたいことはわかるけど、このやり方をみんなパッと見てわかるのだろうか・・・」と思ったからです。
数学がわかっている人には常識かも知れないし、こんなの簡単だよとなるのかも知れませんが、実際の現場には数学を知ってる人ばかりとは思えません。たったの25命令で処理できるクールなロジックなのはわかりますが、普通の人にわかるのかなぁ?と思います。1番目の書き方では、CPU使用量は約5倍ですがたかが131命令です。実際のところ大した違いではありません。数値計算や科学技術計算を延々と繰り返すプログラムなら別ですが、たいていのシステム系プログラムや業務アプリケーションではここまでこだわる必要性はないと考えます。それよりは、後で自分とは別の、普通の人が見てもわかるプログラムとして書く方がいいと考えます。アセンブラーらしいとか、CPUの機能を知り尽くしたようなコードもありなのですが、ソートやデータベースの検索エンジンなど、性能命のソフトウェアならともかく、業務で作るプログラムはわかりやすくないと(後の人が)困ります。そうでなくてもアセンブラーなんてわからないから困るって言うのが現実ですから。