【テクニカル・上級編】LISTコンパイラオプションによるソースコードとアセンブラの対照 – PL/Iの基本構文とデータ制御実践ガイド

PL/Iコンパイラが吐き出すアセンブラと心中する覚悟:`LIST`オプションで剥き出しの機械語を覗く夜

基幹システムの現場で20年以上、IBMメインフレームの底を這いずり回ってきた。
夜中に突然「S0C4が発生した」「バッチの計算結果が1円ズレる」「モダナイゼーションでJavaに置き換えたら動かない」そんな修羅場に直面するたび、我々が最後にすがるのは、キレイに整った高級言語のソースコードではない。コンパイラが吐き出した、あの無機質なアセンブラリストだ。

JavaやC#のエンジニアから見れば、コンパイルリストを出力してアセンブラコードを眺めるなど、化石の時代の遺物に映るかもしれない。しかし、PL/I(Programming Language One)という、かつて「全てを記述できる言語」として設計された化け物を扱う上では、このアセンブラとの対話が不可欠な瞬間が確実に訪れる。

今回は、PL/Iコンパイラの`LIST`オプションに焦点を当て、ソースコードと生成された機械語(HLASM形式のアセンブラ)をどう対照し、現場の難解なバグやマイグレーションの罠をどうやって粉砕してきたのか、その実戦知見を語ろう。

なぜ、今さら「LISTオプション」なのか?

PL/Iは非常に高水準な言語である。構造体、ポインタ、文字列操作、ビルトイン関数、そして何よりデータ属性の暗黙的変換(これが恐ろしいのだが)を勝手にやってくれる。

しかし、その「親切心」の裏で、コンパイラが裏でどんな機械語命令を吐いているかを知らないと、基幹システムの極限のパフォーマンスチューニングや、予期せぬアベンド(ABEND)の解析で足元をすくわれる。

コンパイル時に `LIST`(あるいは `ASMLIST`)オプションを指定すると、ソースコードの各ステートメントが、どのようなS/370(あるいはz/Architecture)の機械語命令群に翻訳されたのかがリストの後半に展開される。

STMT OFFSET HLASM CODE
000125 0002A0 L 15,123(0,13)
000126 0002A4 LA 0,40(0,0)
000127 0002A8 BALR 14,15

この数行の羅列から、レジスタの枯渇、不必要なデータ変換、さらには構造体の不整合によるアライメント例外(S0C4やS0C7)の匂いを嗅ぎ取るのが、真のメインフレーム・アーキテクトの腕の見せ所だ。

1. ベース変数とポインタ操作:アセンブラの目で見るメモリの歪み

まずは、PL/Iの真骨頂であるポインタとベース変数(BASED変数)の挙動を、`LIST`の視点から覗いてみよう。
基幹システムでは、可変長レコードやストレージ直読み込みにおいて、ベース変数を多用する。

以下のコードを見てほしい。

DCL 1 P_REC BASED(P_PTR),
2 REC_ID CHAR(4),
2 REC_BODY CHAR(100);

DCL P_PTR POINTER;
DCL WORK_BUF CHAR(104) STATIC;

/ ポインタの設定 /
P_PTR = ADDR(WORK_BUF);

/ フィールドへの代入 /
REC_ID = ‘M001’;

この一見何気ない代入文 `REC_ID = ‘M001’;` が、コンパイル最適化と絡むと、アセンブラ上でどう展開されるか。`LIST`オプションによるリスト出力を見ると、コンパイラがベースレジスタをどのように割り当て、オフセット計算を行っているかが一目瞭然となる。

ここで注意すべきは、「ベースポインタ自体の指すアドレスが奇数になっていないか」「ストレージ境界の整列(アライメント)が崩れていないか」という点だ。
もし `P_PTR` が適切にフルワード(4バイト境界)またはダブルワード(8バイト境界)にアラインされていないアドレスを指している状態で、数値項目やポインタ型を含む構造体をマッピングすると、アセンブラレベルでは `MVC` 命令の途中で例外が発生するか、最悪の場合、ハードウェアの性能劣化(Cross-memory Reference penalty)を引き起こす。

`LIST`リストで `L`(Load)や `ST`(Store)命令のベース・インデックス・ディスプレースメント形式(例:`5(12,11)`)を追いかけることで、コンパイラがどのレジスタをベースレジスタとして酷使しているか、あるいはレジスタ退避のオーバーヘッドが発生していないかが手に取るように分かる。

2. 最適化オプション(`OPT`)とアセンブラの変貌

PL/Iコンパイラ(Enterprise PL/I)の最適化レベル(`OPT(1)` や `OPT(2)`)を上げると、`LIST`で出力されるアセンブラは劇的に変化する。

無駄なロード/ストア命令が削ぎ落とされ、ループアンローリング(ループの展開)や、レジスタ内での変数保持が行われる。だが、これがレガシー移行時のデバッグにおいて牙をむく。

現場のエッジケース:最適化による「変数の消滅」

JavaやC#への移行プロジェクトで、旧COBOLや旧PL/Iのベンダ提供ソースを解析する際によくあるのが、「元のコードには存在する変数が、最適化されたアセンブラ上ではレジスタに直打ちされてメモリ上に存在しない」という現象だ。

ダンプ解析(CEEDUMPやSYSUDUMP)を行った際、特定の変数のメモリ領域が更新されておらず、「バグだ!」と騒ぐ若手エンジニアがいる。しかし、`LIST` とマップリスト(`MAP`)を突き合わせると、その変数はメモリではなく汎用レジスタ(例: R7)のなかに閉じ込められており、アベンド時点のレジスタスナップを見なければ値が追えないことが判明する。

モダナイゼーションの過渡期において、新旧システムの出力結果を突き合わせる「リグレッションテスト」を行う際、この最適化挙動の差異が原因で「ロジックが違う」と誤認するケースが後を絶たない。移行設計においては、デバッグフェーズでは必ず `NOTEST` ではなく `TEST` オプションや、最適化を抑えた `OPT(0)` でのリストを比較用として保管しておくべきだ。

3. パックデシマル(COMP-3)の内部符号反転バグとアセンブラ検証

金融系システムで最も恐れられるのが、パックデシマル(PL/Iの `FIXED DECIMAL`)の符号異常に起因する `S0C7`(データ例外:Data Exception)である。

例えば、外部からインポートしたファイルや、DB2からのフェッチデータの中に、ゾーン形式や不正な符号ビットが混入していた場合、計算を行った瞬間にシステムはダウンする。

Dcl WS_AMT Fixed Decimal(15,2);
Dcl WS_TAX Fixed Decimal(15,2);
Dcl WS_TOT Fixed Decimal(15,2);

WS_TOT = WS_AMT + WS_TAX;

この演算は、アセンブラ上では `AP`(Add Decimal)や `MP`(Multiply Decimal)といったBLL(Binary-Coded Decimal)命令に翻訳される。
`LIST`リストで確認すると、どのステートメントがどの Decimal 命令に対応しているかが一発で特定できる。

アーキテクトの知見:S0C7防衛網の張り方

アベンドダンプで `S0C7` が発生した際、PSW(Program Status Word)のインストラクションアドレスから、`LIST`リスト上の該当ステートメントを逆引きする。これにより、どの変数(どのオフセット)のどのバイトのニブル(4ビット)が不正だったのかを特定できる。

JavaやC#へ移行する際、この「パックデシマルの不正符号」のハンドリング仕様をどう再現するかは非常に悩ましいポイントだ。メインフレームのハードウェアは不正な符号があっても命令実行時に検知して即座にS0C7を投げるが、JavaのBigDecimalなどは、ライブラリのパース段階で例外を投げるか、あるいはそのままスルーして後続で致命的な業務ズレを起こすことがある。
移行設計では、アセンブラレベルでどのようなハードウェア命令が担保していた整合性を、移行先でどうエミュレートするかを定義しなければならない。

4. 埋め込みSQL(DB2)およびCICS環境におけるLISTの活用

オンラインシステム(CICS)やバッチでのDB2アクセス(SQLホスト変数)が絡むと、PL/Iのソースコードはプリコンパイラによって一旦拡張され、最終的なPL/Iソースを経てアセンブラへと変換される。

ここで、ホスト変数のデータ属性の不一致によるパフォーマンス低下を見抜くためにも `LIST` が役立つ。

/ DB2ホスト変数と構造体 /
EXEC SQL INCLUDE MY_TABLE;

EXEC SQL
SELECT SALARY INTO :MY_SALARY
FROM EMPLOYEE
WHERE EMP_ID = :IN_EMP_ID;

DB2のプリコンパイラは、このSQLを動的・静的な呼出し(`DSNHLI` や `DLI`)に変換する。
`LIST`リストを見ると、SQL文の前後でホスト変数のアドレスをロードし、DB2の通信領域(SQLCA)へデータをコピーする機械語命令群がどのように生成されているかが確認できる。

もし、ホスト変数のデータ型(例:`FIXED BINARY(31)`)と、DB2側の定義(例:`DECIMAL(9,0)`)が微妙にズレていると、コンパイラやプリコンパイラが裏で暗黙のデータ変換ルーチン(サブルーチンコール)を挿入する。
これが大量データのバルク処理(Cursorループなど)の中で発生すると、CPU使用率(SST/CST)が跳ね上がる原因になる。`LIST`リスト上に突如として現れる見慣れないサブルーチン呼出し(`L 15,=V(DHC… )` のような外部参照)を見逃さないことだ。これが、高負荷トラブルの犯人を暴く決定的な証拠となる。

5. まとめ:レガシーの底を知る者が、モダナイゼーションを制す

「古いシステムのことは黒箱(ブラックボックス)にして、さっさとJavaに書き換えればいい」
そんな甘い言葉を囁くコンサルタントや、中身のメカニズムを理解せずに自動変換ツール(コンバーター)に頼り切ったプロジェクトは、もれなく本番稼働後のパフォーマンス劣化や、原因不明のデータ不整合という名の地獄を見る。

PL/Iの `LIST` オプションを使いこなし、ソースコードとアセンブラの1対1の対応関係を脳内で展開できるスキルは、単なる懐古趣味ではない。それは、「システムがハードウェアの上でどのように息をしているか」を把握するための、最も確実で信頼できるレンズなのだ。

これからレガシーマイグレーションを主導するテックリードやアーキテクトたちよ。
コードの表面だけをなぞるな。コンパイルリストを開き、機械語の息吹を感じ取れ。そこにこそ、真のシステム信頼性を担保する答えが眠っている。

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