シンボル(記号)*を活用する

アセンブラーにはロケーション・カウンターという仕組みがあります。アセンブラー言語でプログラムを書く際は、B ERROR、ST R1,AREA2のように命令やデータ領域を名前で指し示すことができます。本来ならば B 324(,R12)、ST R1,248(,R12) のように、分岐先の命令やデータ領域のアドレスをベース・レジスター番号と変位もしくはベース・レジスター番号とインデックス・レジスター番号および変位の組み合わせで書かなければなりません。
しかしながら、ベース・レジスターやインデックス・レジスターの番号はともかくとして、変位に関しては簡単には求められません。参照しようとしているデータ領域が、プログラムやテーブルの先頭から何バイト離れているところにあるのかを正確に求めることはとても大変です。プログラムの先頭から書いた1つ1つの命令の長さを積み上げ、境界調整などが入っていればそこでのアドレスのずれを調整し、データ領域なら変数や定数の長さなどを正確に計算して行かなければなりません。作業自体も大変ですが、一番の問題は間違えやすいということでしょう。プログラムの先頭に近い命令の変位計算で間違えれば、そこから後ろ、つまりそのプログラムで書いた命令やデータ領域のほとんどの変位も間違ってしまいます。こうなるとアセンブラー言語は難しいとか、嫌いとかではなく、そもそも使い物にならなくなってしまいます。
それ故、アセンブラー言語では命令やデータ領域を機械命令が規定する直接の形式(ベース・レジスター番号と変位の組み合わせ)だけでなく、記号(名前)で指定できるようになっているのです。しかし、最終的なオブジェクト・モジュールにする際は、記号が示す命令やデータ領域の位置を機械命令が規定するベース・レジスター番号と変位の組み合わせに変換しなければなりません。正確な変位を計算するために使用されるのがロケーション・カウンターです。

プログラム内の各モジュールは、セクションという単位に区分され、命令やデータの集合体はCSECT(制御セクション)、データ領域のレイアウト定義はDSECT(見かけセクション)と呼ばれます。一般的にはソース・コードの最初のCSECTがプログラム・モジュールの先頭になり、アセンブラーの内部カウンターであるロケーション・カウンターも最初のCSECTが先頭(0)になります。LRなどの2バイト命令を1つ書けばロケーション・カウンターは2つ進み、STやAなどの4バイト命令を1つ書けばロケーション・カウンターは4つ進みます。F型定数を1つ書けばロケーション・カウンターは4つ進み、境界調整が入れば4バイト・バウンダリーに合わせるためのずれの分だけ1~3つ進みます。20バイトの文字列領域を定義すればロケーション・カウンターは20進みます。このようにして、プログラム中に記述された各々の命令やデータ領域がセクションの先頭からどの位離れているかの離れ度合いをロケーション・カウンターによって管理します。

上記に述べたように、アセンブラー言語でもCOBOLやPL/Iなどと同じように命令やデータ領域を記号(名前)で示すことができます。しかし、アセンブラー言語では名前ではなく変位(ロケーション・カウンター)の値自体を積極的に参照して命令やデータを定義したい場合もあります。その際に使用されるのが記号文字 * です。
*は、現在のロケーション・カウンターの値を示します。今自分がいる位置から何バイト先といった表現をしたい場合や、今自分がいる位置からテーブルの先頭位置を引き算してテーブル領域の長さを求めたいといったことが簡単にかつ正確に記述できます。

*によって、ベース・アドレスを示す

*記号の使用例の最も代表的なものの1つです。CSECTの直後の*は、プログラム(CSECT)の先頭を示します。つまり、プログラムからの変位0の位置を示すことになります。USING *,12 は、レジスター12番をUSING命令を書いた位置のベース・アドレスを示すベース・レジスターにするという定義です。プログラムの先頭でベース・レジスターを定義する場合は、プログラム名の代わりに*を指定できます。

上記と全く同じプログラム・コードを*記号を使わないで書き換えたものです。PROGNAMEを変更する場合は、2箇所を同時に直す必要があります。2箇所でも3箇所でもエディターのChangeコマンドで簡単にできるとは考えないで下さい。将来の保守を考えて修正箇所をなるべく少なくするという意味では、記号*を使用する方がベターです。

同じ名前を繰り返し記述することを避ける方法として、名前をそのものを直接記述するのではなく可変記号で記述して、実際の値(この場合はプログラム名)をSETC命令で可変記号に設定するという方法もあります。このようなコーディング方法も保守性を高める手段の1つですが、この記事は記号*を活用するということなので、あくまでもアドレス位置の指定に*記号を使うことでの説明を続けます。どのように書いてもわかりやすく、間違えにくく、保守性も高いというコーディングをすればいいのです。そのためにもいろいろな書き方があること知っておくといいです。

比較的古いプログラムに多い書き方ですが、GR15に格納されている入口点アドレスを使わずにBALR命令で自分自身のメモリー上のアドレスをロードする方法です。この場合、ベース・アドレスはプログラムの先頭ではなくプログラムの先頭+6番地になります。STM命令で4バイト、BALR命令で2バイトの合わせて6バイト分の命令コードが埋め込まれるからです。BALR命令の結果、BALR命令の次の命令アドレスがGR12に入ります。したがって、プログラムのベース・アドレスも先頭から6バイト離れた位置にUSING命令で設定する必要があります。

「CSECTの直後にUSINGを書く」と固定化して考えてしまうと上記のような定石とはかけ離れたコーディングをすることになります。誤りではありませんが、わかりにくく、次にそのプログラムをメンテナンスする人が不思議がることになります。1つ前の例のように、BALR命令の次に*記号を使ったUSING命令を書く方が自然です。ベース・レジスターにはBALR命令の次のアドレスが読み込まれるので、その位置をベース・アドレスに設定するUSING命令を書くわけです。その際にロケーション・カウンターの値を示す*記号を使えば、プログラムの先頭をベース・アドレスにする場合でも少し離れた位置をベース・アドレスにする場合でも同じコーディング方法でUSING命令を書くことができます。記号*は、今現在の自分自身の位置(変位)を示すということを知っていれば、アセンブラー・プログラムのコーディングの様々な場面で応用できます。

命令列内のデータ領域を迂回する

レジスター保管域をハウス・キーピングのコードの中に定義した例です。レジスター保管域のように実行中に参照することがないデータ領域にいちいち名前を付けるのも面倒です。筆者自身は好んで使っていましたが、間違いの元だからきちんとラベルを付けてジャンプするようにと言う人もいるでしょう。

きちんとラベルを付けるとこんな感じです。個人的には好きになれない書き方です(あくまでも面倒という理由です)。レジスター保管域の長さなどは基本的に変わらないから、飛び先の変位を直接書いても問題ないと思います。分岐先を *+4+72 のように書くことは、元々はOSのマクロ命令から習ったことです。OSでもやってるんだから、まぁいいよねと。もし、誰かに突っ込まれてもOSのマクロでもこういうコーディングしてますと返せます。OSでもやってると言うとたいていの人は反論しません。でも、OSはこういう理由でやっている、アプリケーションはこういう理由からそのような方法は採らないと説明されれば素直に従いましょう。そのような説明ができる人ならエンジニア、プログラマーとして信頼できるでしょう。

OPENマクロの展開形です。SVC呼び出しのパラメーター領域を、BAL *+8で8バイト先に(BAL命令が4バイト、パラメーターが4バイト)分岐して命令列中のパラメーター領域を迂回しています。昔これを見て *と+n の組み合わせで飛び先の変位を直接書いてしまう方法を知りました。もちろん、迂回するロジック内の命令やデータがしょっちゅう増えたり減ったりする場合は使えませんが、1度コードを書けば基本的に変わらない部分に*記号で変位を直接書くことはOSでもやっているコーディング・テクニックなのだと学んだわけです。

データ領域の長さや個数の計算に使う

データ領域の定義でも、現在位置を示す記号*はいろいろな目的に使えます。テーブル構造の領域を定義するような際、テーブルを構成するエントリーの長さは、計算しなくても*記号とテーブルの先頭を示す名前の引き算の式で定義できます。この例では、TABENTLN EQU *-TABLEの部分です。なお、DC (TABENTLN*(TABENT#-1))X’00’における*は、位置を示す記号ではなく掛け算を示す記号です。

同じような領域の定義ですが、こちらはレコードのレイアウト定義です。レコードの長さは256バイトとして決めてあります。先頭から必要な項目を並べていき、残りを予約領域として定義するような場合、残りが何バイトあるかをいちいち計算する必要はありません。DC (RECORD+RECORDLN-*)X’00’のように、最後のフィールドの次の位置からRECORDの先頭位置を引き算してそれをレコード長から引けば残りのバイト数が計算できます。それを1バイト領域の複写因数にすれば残り領域を正確に埋めることができます。
アセンブラー言語でのデータ領域の定義では、記号*を使う使わないに関わらず、長さや個数などは直接自分で計算して書くのではなく式を使ってアセンブラーに計算させることを覚えましょう。EXCELでの表計算のように、1箇所直せば連動して他の関連する部分も自動計算されるようにしておくと将来のメンテナンスも楽ですし何より正確です。人間は単純な足し算引き算でも間違えます。

配列内の連続したデータの定義に使う

上記のように定義すると、x00からxFFまで1ずつ増やした1バイトの値を256個並べた配列領域を定義できます。実際にアセンブルすると以下のようになります。

上記のように定義すると、xC1からxC9、xD1からxD9、xE2からxE9までの1バイトの値を26個並べた配列領域を定義できます。これはEBCDICコードの文字AからZを示します。文字AはEBCDICコードでxC1(193)、文字JはEBCDICコードでxD1(209)、文字SはEBCDICコードでxE2(226)であることを応用しています。変位の位置も実際にアセンブルすると以下のようになります。

ARRAYもALPHAも、それぞれの領域内の定数の長さは1ですから(複写因数が個数を示す、1バイトが何個という定義をしている)、*が示す変位も1ずつ増えます。ALPHAやARRAYが示すデータ領域の先頭の位置は変わりませんから、記号*が示すロケーション・カウンターの値は1ずつ増えていくことを利用したデータ定義の例です。