メインフレームの深淵:PL/Iの「参照渡し」が引き起こす静かなる破壊と、その処方箋
メインフレームの現場で長く生きていると、「なぜか本番環境でだけ値が壊れる」という怪奇現象に何度遭遇することか。その多くは、PL/Iのデフォルトである「参照渡し(BYADDR)」の挙動を、モダン言語の感覚で捉えてしまったことに起因する。
今日は、JavaやC#のモダンな言語仕様に慣れたアーキテクトこそが陥りやすい、PL/IのCALL文における内部挙動と、それが引き起こす悪夢のようなデバッグ事案について、腰を据えて話そうと思う。
—
1. 参照渡し(BYADDR)の正体:ポインタの先にある「生」のメモリ
PL/Iにおいて、引数を明示せずに`CALL SUB(VAR)`と記述した場合、コンパイラはデフォルトで`BYADDR`を選択する。これは、呼び出し元の変数の「アドレス」をそのままレジスタ経由(あるいはパラメータリスト経由)で呼び出し先に渡すことを意味する。
ここでの肝は、呼び出し先でその値を変更すれば、呼び出し元のメモリ領域が直接書き換わるという点だ。
/i
/ 呼び出し元 /
DCL TARGET_VAL FIXED DEC(5) INIT(100);
CALL SUB_PROC(TARGET_VAL);
/ ここでTARGET_VALは呼び出し先で変更され、999になっている /
SUB_PROC: PROC(ARG);
DCL ARG FIXED DEC(5) BASED; / アドレスを受け取り、それをベースとして扱う /
ARG = 999; / 直接メモリを書き換える /
END SUB_PROC;
一見すると効率的だが、これが大規模な基幹システムで「意図しない副作用」を招く。特にCICSオンライン処理のように、ストレージを動的に管理し、複数のモジュールが同じメモリ領域を指し示している場合、一箇所の値の書き換えが致命的なアベンド(S0C4やS0C7)を誘発する。
—
2. コンパイラオプションと「符号反転」の罠
特に注意すべきは、パックデシマル(FIXED DEC)の受け渡しだ。PL/Iコンパイラは、呼び出し先で定義されたデータ型が呼び出し元の引数と微妙に食い違っている場合、暗黙の型変換を行うことがある。
例えば、呼び出し先で`FIXED DEC(5)`として受け取るつもりが、実際には`FIXED BIN(15)`が渡されていた場合、コンパイラは一時的なワークエリア(テンポラリ)を生成し、そこに値をコピーして渡す。しかし、型定義が一致していれば、前述の通り「直打ち」だ。
現場でよくあるトラブル:
DB2の埋め込みSQLで取得したパックデシマル値を、そのままサブルーチンに渡し、サブルーチン側で独自のロジックを通した際、内部表現の「符号」が正しく処理されず、DBへの書き込み時に`SQLCODE -802`(算術例外)や、最悪の場合はデータの符号が反転してレコードが破壊されるケースだ。
これを避けるためには、コンパイラオプション `LIMITS(FIXEDDEC(…))` や `RULES(NOLAXDCL)` などの設定だけでなく、呼び出し先の引数定義に `BYVALUE` を明示的に指定し、コピー渡しに限定するというのも一つの防衛策である。
—
3. ダンプ解析とポインタの追跡
アベンドが発生した際、SYSUDUMPやCEEDUMPを眺めても、ポインタが指す先が「ゴミ」になっていれば、それはもう手遅れだ。
私が推奨するデバッグの鉄則は、「動的なメモリ領域のアドレスをトレースする」ことだ。PL/Iの `ADDR` ビルトイン関数と `STORAGE` 属性を組み合わせ、プログラムの実行ログにポインタ値を書き出しておけば、事後の解析が劇的に楽になる。
特に、CICSの `GETMAIN` で確保した領域を別モジュールに渡す場合、以下の点を確認してほしい。
1. ポインタのオフセット: ベース変数のポインタが、期待した領域の先頭を指しているか。
2. ストレージの重複: `BASED` 変数を使用している場合、`ALLOCATE` を忘れていないか、あるいは `FREE` した後のメモリを使い続けていないか。
—
4. マイグレーションにおける「思考の転換」
JavaやC#への移行を検討している諸君へ。これらの言語は基本的に参照型を除き、プリミティブ型は「値渡し」である。PL/Iの `BYADDR` をそのまま `ref` や `out` キーワードで置き換えれば済むという安易な設計は、必ずどこかで綻びる。
移行先で同じ挙動を再現しようとするのではなく、むしろ「副作用を排除した関数型に近い設計」へ書き換えるべきだ。変数の値を破壊的に変更するロジックを廃し、関数の戻り値として新しい値を得る構造に作り変えること。これが、レガシーシステムを次世代の堅牢なアーキテクチャへと昇華させる唯一の道である。
最後に
PL/Iの仕様は、かつての計算機リソースの制約が生んだ「極限の効率化」の結晶だ。しかし、その力は諸刃の剣でもある。
もし諸君が今、大規模な改修や移行に頭を悩ませているのなら、まずはコンパイラの生成するリストファイルと、アベンド時のダンプに語りかけよ。機械は嘘をつかない。たとえ、その挙動がどれほど難解で、非人間的であったとしてもだ。
何か具体的なアベンド解析や、特定の移行パターンで詰まっていることがあれば、いつでも聞かせてくれ。メインフレームの深淵は、意外と温かいものだ。
