【テクニカル・上級編】ENTRY属性と引数渡し(BYADDR/BYVALUE)の規約 – PL/Iの基本構文とデータ制御実践ガイド

はじめに:PL/Iの「予約語レス」な世界と、引数渡しの深淵

こんにちは。長年、IBMメインフレームの基幹システムでCOBOLとPL/Iの泥水をすすってきたシステムアーキテクトだ。

現代のJavaやC#などのモダン言語に慣れ親しんだエンジニアが、初めてPL/Iのコード、特に他言語連携やポインタ操作が絡むサブルーチン群を目にすると、決まってこう驚嘆する。
「なんだこの言語は。変数名に `IF` や `READ` が使えてしまう上に、ポインタと参照渡しがコンパイラオプション一つで全く違う振る舞いをするぞ、と」

そう、PL/IにはC言語のような厳格な「予約語(Reserved Words)」という概念が存在しない。すべてが「文脈依存語(Contextual Keywords)」である。変数名に `GO TO` や `THEN` と名付けることすら理論上は可能だ(もちろん、そんな狂った真似をするプログラマは正気ではないが)。この仕様の裏には、コンパイラが前後の文脈を完璧に解析して解釈するという、当時のIBMの狂気的なまでの自然言語処理的アプローチが隠されている。

しかし、この「自由度の高さ」は、レガシーシステムのブラックボックス化を加速させ、マイグレーション時の最大の足かせとなる。特に、`ENTRY` 属性と引数渡し(`BYADDR` / `BYVALUE`)の規約、そして他言語(COBOLやC)とのインターフェース設計を誤ると、本番稼働後に突如として S0C4アベンド(ストレージ保護例外) や パックデシマルの内部符号反転による数値化け という、悪夢のようなトラブルを引き起こす。

今回は、テックリードやマイグレーションアーキテクトに向けて、この引数渡しのメカニズムとエッジケース対策を、実務の現場目線で骨の髄まで解説しよう。

—

1. `BYADDR` と `BYVALUE`:デフォルトの罠とストレージレイアウト

PL/Iのサブルーチン呼び出しにおける最大の注意点は、引数のデフォルト渡し方式が `BYADDR`(アドレス渡し) であるという点だ。C言語のデフォルトが `BYVALUE`(値渡し)であるのとは真っ向から対立する。

COBOLと連携する場合、COBOL側は基本的にすべて参照渡し(POINTERを通したアドレス渡し)であるため、PL/I側が `BYADDR` であれば阿吽の呼吸で一致する。しかし、C言語(Language Environment環境下のLE-Cなど)や、一部の最適化された外部ルーチンと連携する際、この前提が崩壊する。

実務コード例:`BYADDR` と `BYVALUE` の明示的な制御

以下のPL/Iコードを見てほしい。ここでは、C言語で書かれた高速ハッシュ計算ルーチンを呼び出すシーンを想定している。

1
/ ———————————————— /
/ PL/I Main Program: 外部Cルーチン呼び出しと引数制御 /
/ ———————————————— /
DEMO_MAIN: PROC OPTIONS(MAIN);

DCL CALC_HASH ENTRY
(
CHAR(100) BY ADDR, / 文字列はデフォルトでBYADDR /
FIXED BIN(31) BY VALUE / 数値フラグを明示的にBYVALUEで渡す /
)
RETURNS(FIXED BIN(31))
EXT(‘CALCHASH’); / 外部Cシンボル名とのリンク /

DCL TARGET_STR CHAR(100) INIT(‘IBM_MAINFRAME_Z16’);
DCL HASH_FLAG FIXED BIN(31) INIT(1);
DCL RESULT_CODE FIXED BIN(31);

/ C言語側はBYVALUEで受け取る数値を期待しているため、 /
/ BYVALUEの指定が抜けると、アドレス値そのものが数値として渡され暴走する。 /
RESULT_CODE = CALC_HASH(TARGET_STR, HASH_FLAG);

PUT SKIP LIST(‘HASH RESULT CODE: ‘ || RESULT_CODE);

END DEMO_MAIN;

もし、`CALC_HASH` のプロトタイプ宣言で `FIXED BIN(31)` に `BYVALUE` を付け忘れた場合、PL/Iは `HASH_FLAG` の変数自体の「アドレス値(例: `200045A0` のような31ビットの値)」をC言語に渡してしまう。C言語側はそれを通常の整数値(`1` なのか `536892832` なのか)として処理するため、結果の不整合や、最悪の場合はメモリー破損によるS0C4アベンドを引き起こす。

—

2. ベース変数とポインタを用いた動的ストレージ操作のエッジケース

メインフレームの基幹バッチにおいて、可変長レコードや動的なワークエリアを扱う際、PL/Iの `POINTER` と `BASED` 変数の組み合わせは不可欠だ。しかし、ここにもアーキテクトが頭を抱えるポイントがある。

構造体の不整合とアライメント(Alignment)

PL/Iコンパイラは、デフォルトでハードウェアのアクセス効率(境界整列)を考慮し、構造体のメンバー間にパディング(空き領域)を自動挿入する。一方、COBOLの `01` レベルや、C言語の `#pragma pack` なし構造体では、パディングのルールが異なる場合がある。

これを意識せずにポインタ経由でストレージを共有すると、数値データ(特に `FIXED DECIMAL` や `FLOAT`)の読み込み時にデータがズレ、S0C7アベンド(データ例外:不当なパック10進数) の直撃を受けることになる。

1
/ ———————————————— /
/ BASED変数とポインタによる動的ストレージマッピング /
/ ———————————————— /
UNALIGNED_MAPPING_SAMPLE: PROC OPTIONS(MAIN);

/ UNALIGNED属性を付与することで、コンパイラによるパディングを抑制し、 /
/ COBOLやCとのバイナリ互換性を強制的に担保する。 /
DCL 1 ACCOUNT_RECORD BASED(PTR_ACCT),
3 ACCT_ID CHAR(8) UNALIGNED,
3 ACCT_BALANCE FIXED DEC(11,2) UNALIGNED;

DCL PTR_ACCT POINTER;
DCL RAW_BUFFER CHAR(20) BASED(PTR_BUF);
DCL PTR_BUF POINTER;

/ 外部から取得した生データ(ゲッタウェイストレージ)のポインタを割り当てる /
/ ※ここではイメージとしてGETMAIN相当の処理を想定 /
/ PTR_BUF = …; /

/ BASED変数にポインタをバインド /
PTR_ACCT = PTR_BUF;

/ パディングなし(UNALIGNED)のため、オフセットのズレなく安全にアクセス可能 /
IF ACCT_BALANCE < 0 THEN PUT SKIP LIST('OVERDRAWN ACCOUNT: ' || ACCT_ID); END UNALIGNED_MAPPING_SAMPLE; アーキテクトの知見:
マイグレーション時にJava等のオブジェクト指向言語へデータを引き渡す際、この `UNALIGNED` の指定漏れによるバイナリ破損は非常に多い。C#やJava側でバイト配列(`byte[]`)としてストリームを解釈する際、PL/I側が意図せぬパディングを挟んでいると、フィールドの切出し位置がズレてデバッグに数日を費やす原因になる。外部連携する構造体には、必ず `UNALIGNED` を明記するべきである。

—

3. コンパイラオプションの魔力:`RENT`, `OPTIMIZE`, そして `TRAP`

メインフレームのPL/I最適化コンパイラ(Enterprise PL/I for z/OS)は極めて高度なコード生成を行うが、コンパイラオプションの指定一つでプログラムの生死が決まる。

1. `RENT`(リエントラント / 再入可能)
CICS環境やマルチスレッドバッチで稼働させるプログラムは、必ず `RENT` でコンパイルされなければならない。静的変数(`STATIC`)への書き込みを行っているレガシーコードを `RENT` 化しようとして、マルチタスク競合(データの上書き)によるし尿の出ないような不可解なバグに悩まされたアーキテクトも多いはずだ。
2. `TRAP(ON)` とアベンドダンプ
PL/Iでゼロ除算やポインタ違いが発生した際、Language Environment (LE) がトラップして独自のメッセージ(IBM933Iなど)を出して終了することがある。しかし、SYSUDUMPやCEEDUMPを確実に採取し、どのステートメントで、どのレジスタが破壊されたかを特定するためには、コンパイル時およびランタイムでの `TRAP(ON, SPIE)` などの設定が不可欠である。

—

4. マイグレーション(レガシーモダナイゼーション)における実務的防衛策

もし貴殿のプロジェクトが、PL/Iで書かれた巨大な基幹システムをJava(Spring Boot)やC#(.NET Core)へリライト、あるいはオープン系へのリホストを計画しているなら、以下の「エッジケース対策」を設計書の前提として叩き込んでおくべきだ。

  • 数値型の精度と丸め誤差の検証

PL/Iの `FIXED DECIMAL`(パック10進数)は、小数点以下の演算において独特の丸め動作を行う。Javaの `BigDecimal` へ移行する際、スケール(桁数)と丸めモード(Rounding Mode:四捨五入か切り捨てかなど)を完全に一致させないと、決算系バッチの総額で1円のズレ(端数誤差)が発生し、業務部門から激しい差し戻しを受けることになる。

  • 暗黙のデータ変換の排除

PL/Iは型が異なる変数同士の演算であっても、コンパイラが気を利かせて勝手に暗黙の型変換(Promotion)を行ってくれる。これがコードの可読性を下げると同時に、マイグレーション先での厳格な型チェック(ClassCastException等)に引っかかる原因となる。移行前段階のコード棚卸し(リファクタリング)において、明示的なキャスト関数へ書き換えておくことが極めて有効だ。

—

おわりに:レガシーの呪縛を解くために

PL/Iの `ENTRY` 属性や `BYADDR`、ポインタ操作の規約は、メインフレームのハードウェア資源が極限まで限られていた時代に、パフォーマンスを極限まで絞り出すために最適化された「芸術的なまでに尖った仕様」である。

しかし、現代のシステムアーキテクチャにおいて、そのブラックボックス化された挙動はリスクでしかない。
コードの行間にあるコンパイラの暗黙の挙動を読み解き、ストレージのレイアウトや外部インターフェースの規約を完全にコントロールすること。それこそが、レガシーシステムを安全に現代へbridging(架橋)するための、私たちシニアアーキテクトに課された使命である。

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