【実務・中級編】SUBSTR関数の内部実装とアセンブラ展開 – PL/Iの基本構文とデータ制御実践ガイド

こんにちは、現場のエンジニア諸君。日々の大規模バッチの保守や、果てしないレガシーマイグレーションの調査、本当にお疲れ様だ。

さて、今回はPL/Iのデータ制御と、コンパイラの「裏側の挙動」について深く掘り下げていこう。テーマは 「SUBSTR関数の内部実装とアセンブラ展開」 だ。

「おい、先輩。文字の切り出しなんて `SUBSTR(VAR, 1, 10)` と書けば動くだろ? なんでわざわざアセンブラ展開なんて気にする必要があるんですか?」
そんな声が聞こえてきそうだな。しかし、基幹システムの現場でパフォーマンスチューニングを行うとき、あるいはコンパイラのバージョンアップに伴う予期せぬCPU時間の急増(スルーブットの悪化)に直面したとき、この「裏側の仕組み」を知っているかどうかが、一流のメインフレームアーキテクトと、単なるコードコピペ職人を分ける分水嶺となるのだ。

今日は、PL/Iが誇る強力な組込み関数(BUILTIN)である `SUBSTR` が、コンパイラによってどのように料理され、IBM Zのハードウェア命令(マシン語)に変換されるのか、その泥臭くも美しい最適化の世界へ君たちを案内しよう。

—

1. 予約語を持たないPL/Iの懐の深さと「SUBSTR」の正体

まず大前提として、PL/IにはC言語やJavaのような「予約語(Keyword)」の概念がほとんど存在しない。`IF` や `DO` でさえもコンテキストキーワードであり、極端な話を言えば変数名として使うことすら可能だ(もちろん、そんな狂ったコーディングをするプログラマは現場から即座に排除されるがね)。

この「予約語を持たない」という言語仕様は、コンパイラの字句解析(Lexical Analysis)において非常に厄介な問題をもたらす。しかし同時に、`SUBSTR` のような強力な機能を、構文エラーを起こすことなく柔軟に組み込める理由でもある。

`SUBSTR` は単なる関数ではなく、`BUILTIN`(組込み関数) として宣言される。これにより、コンパイラはそれがユーザー定義の変数やプロシージャではなく、言語処理系が直接最適化コードに置き換えるべき特別な識別子であることを認識するのだ。

—

2. SUBSTR関数の内部実装:コンパイラは如何にしてマシン語を生成するか

では、本題に入ろう。私たちが何気なく書く `SUBSTR` は、コンパイル時にどのように機械語へ翻訳されているのか。

PL/Iコンパイラ(Enterprise PL/Iなど)は、`SUBSTR(target, offset, length)` の引数の型や定数・変数の属性を綿密に解析し、最も効率的なアセンブラ命令を生成する。ここには大きく分けて2つのパターンが存在する。

パターンA:オフセットと長さが「定数」の場合(コンパイル時最適化)

もし君たちが以下のようなコードを書いたとしたら、コンパイラは実行時に余計な計算を一切行わないようにコードを最適化する。

DCL IN_RECORD CHAR(100);
DCL OUT_FIELD CHAR(10);

OUT_FIELD = SUBSTR(IN_RECORD, 15, 10);

この場合、コンパイラは実行時にオフセット(15)や長さ(10)をレジスタにロードして計算するような無駄な処理を省く。
アセンブラレベルでは、ベースアドレスに対して静的なディスプレイスメント(変位)をあらかじめ計算し、単一の `MVC`(Move Character)命令、あるいはストレージ間の直接コピーへと直結させる。CPUサイクルを1つたりとも無駄にしない、これがIBMメインフレームの真骨頂だ。

パターンB:オフセットや長さが「変数」の場合(動的レジスタロード)

問題は、オフセットや長さが実行時変数である場合だ。例えば、VSAMファイルから読み込んだレコードの可変長部分を切り出すために、以下のようなロジックを書いたとする。

DCL IN_RECORD CHAR(100);
DCL OUT_FIELD CHAR(50);
DCL W_POS FIXED BIN(31,0) INIT(20);
DCL W_LEN FIXED BIN(31,0) INIT(30);

OUT_FIELD = SUBSTR(IN_RECORD, W_POS, W_LEN);

この瞬間、コンパイラは生成するアセンブラコードの中で、以下のようなレジスタ操作とメモリ参照のセットアップを自動的に行う。

1. ベースアドレスの解決: `IN_RECORD` の先頭アドレスを基底レジスタ(例: GPR 3)にロード。
2. オフセットの加算: 変数 `W_POS` の値をワークレジスタ(例: GPR 4)にロードし、1-originから0-originへの調整(`-1` の減算処理)を行った上で、基底アドレスに加算。
3. 長度の設定とMVC/MVCLの選択:

  • 長さが255バイト以下の場合:`EX`(Execute)命令と `MVC` 命令の組み合わせ、あるいはインライン展開された最適化ルーチンに持ち込まれ、指定された長さ(`W_LEN` の値)がレジスタにセットされる。
  • 長さが255バイトを超える長大データの場合:`MVCL`(Move Character Long)命令が呼び出され、偶数・奇数レジスタペアにソースとターゲットのアドレスおよび長さを格納して、ハードウェアレベルの高速転送が実行される。

—

3. 実践:VSAMレコード入出力とONユニット制御におけるSUBSTR活用

百聞は一見にしかず。実際のバッチプログラムで、VSAM(KSDS)のレコードを読み込み、可変長のキー情報や管理領域を `SUBSTR` で安全に切り出し、エラー時は `ON` ユニットでトラップする実用的なコードを見てみよう。

大文字記述、適切なインデント、そして `BUILTIN` 属性の明示的な宣言という、我がチームのコーディング標準に則ったサンプルだ。

/ ————————————————– /
/ PROGRAM-ID: SUBPR01 /
/ REMARKS : VSAMレコード分解とSUBSTR最適化のサンプル /
/ ————————————————– /
SUBPR01: PROC OPTIONS(MAIN);

/ 組み込み関数の明示的宣言 /
DCL SUBSTR BUILTIN;

/ VSAM入力レコード定義 (合計 200バイト) /
DCL 1 VSAM_RECORD,
5 REC_TYPE CHAR(2), / レコード種別 /
5 REC_BODY CHAR(198); / ボディ部分 /

/ 作業変数 /
DCL W_STATUS CHAR(2) INIT(‘OK’);
DCL W_OFFSET FIXED BIN(31,0) INIT(5);
DCL W_EXTRACT_LEN CHAR(10);
DCL EOF_FLAG BIT(1) INIT(‘0’B);

/ ファイル定義 (VSAM KSDS) /
DCL INFILE FILE RECORD INPUT
ENVIRONMENT(VSAM);

/ エラータップ用 ONユニット (入出力エラー) /
ON ERROR
BEGIN;
DISPLAY(‘CRITICAL ERROR OCCURRED IN VSAM PROCESSING‘);
W_STATUS = ‘NG’;
GOTO ERROR_HND;
END;

/ ファイルオープン /
OPEN FILE(INFILE);

/ メイン処理ループ /
DO WHILE(^EOF_FLAG);

/ レコード読み込み /
READ FILE(INFILE) INTO(VSAM_RECORD);

IF EOF_FLAG THEN LEAVE;

/ ———————————————- /
/ SUBSTR関数の利用例 (変数をオフセットに指定) /
/ ※コンパイラによりレジスタロードとMVCへ展開される /
/ ———————————————- /
IF SUBSTR(VSAM_RECORD, 1, 2) = ‘AB’ THEN
BEGIN;
/ オフセットを変数W_OFFSET(5)で動的に切り出し /
W_EXTRACT_LEN = SUBSTR(REC_BODY, W_OFFSET, 10);
DISPLAY(‘EXTRACTED DATA: ‘ || W_EXTRACT_LEN);
END;

END;

CLOSE FILE(INFILE);
RETURN;

ERROR_HND:
DISPLAY(‘PROGRAM TERMINATED WITH ERROR STATUS: ‘ || W_STATUS);
CLOSE FILE(INFILE);
SIGNAL FINISH;

END SUBPR01;

—

4. ベテランからのデバッグのコツ:なぜSUBSTRで例外が起きるのか?

現場でよくあるトラブルが、`SUBSTR` を使った際の 「StringRange(STRG)Condition」、すなわち文字列範囲外参照エラーだ。

コンパイラは賢いので、定数範囲であればコンパイル時に検知してくれるが、先ほどのサンプルコードのように変数 `W_OFFSET` や `W_LEN` を使った動的な切り出しの場合、実行時に以下のような悲劇が起きる。

  • 切り出し元変数の長さを超えたオフセットを指定してしまった。
  • データの終端を超えてメモリを読み込もうとした(S0C4に近い現象を引き起こす、あるいはPL/Iランタイムによる `IBM0221S String range overflow` の検知)。

対策とチューニングの鉄則

1. ストリングレンジチェックの有効化/無効化:
本番稼働中のバッチプログラムで、過剰な `CHECK(STRG)` や `RANGE` オプションを有効にしていると、すべての `SUBSTR` 実行時に境界チェックのオーバーヘッド(アセンブラでの条件分岐命令の追加)が発生し、CPU時間を無駄に消費する。

  • 開発・テスト環境:`STGZ` や `CHECK` を有効にしてバグを早期発見する。
  • 本番環境:十分にテストされ、境界違反があり得ないことが担保された高負荷バッチでは、コンパイラオプションで `NOSTGZ` を指定し、不要なチェック命令を排除してハードウェアの限界性能を引き出す。

2. 変数のデータ型の統一:
`SUBSTR` の第2・第3引数に渡す変数は、必ず `FIXED BIN(31,0)` または `FIXED DEC` の適切な型を使用すること。異なる型(例えば `FIXED BIN(15,0)` や `CHAR` からの暗黙の型変換)を渡すと、コンパイラが余分なレジスタ間の型変換命令(パック10進数とバイナリの変換など)を生成し、パフォーマンスがガタ落ちする原因になる。

—

おわりに

PL/Iの `SUBSTR` は、単に文字列を切るための便利な道具ではない。コンパイラの内部でIBMメインフレームのアーキテクチャ(MVC, MVCL, EX命令、汎用レジスタの割り付け)と密接に結びついた、非常に洗練された言語機能なのだ。

「動けばいいや」ではなく、「この一行が、コンパイラによってどのような機械語に翻訳され、ハードウェアの上でどう流れているのか」を想像しながらコードを書く。それこそが、メインフレーム・システムアーキテクトとしての醍醐味であり、君たちの市場価値を何倍にも高める武器となる。

さあ、今日の業務に戻るとしよう。次回のコードレビューで、無駄な型変換や非効率な文字列操作を見つけたら、今日学んだ知識を活かしてビシッと指導してやってくれ。期待しているぞ!

タイトルとURLをコピーしました