【PL/I × CICS】構造体マッピングの闇:アライメント、符号反転、そして動的ストレージの罠
メインフレームの現場で長年システムを支えてきた者なら、一度は深夜のトラブルシューティングで冷汗をかいた経験があるはずだ。オンライン画面から入力された電文が、CICSの `EXEC CICS READ` を経由してPL/Iの構造体に格納された瞬間、なぜか数値データが化け、あるいは原因不明のASRA(S0C7やS0C4)アベンドを引き起こす――。
JavaやC#といったモダンな言語の世界からやってきたエンジニアは、「なぜ構造体をそのまま通信バッファにマップするだけで、これほどまでに神経を使わなければならないのか」と驚きを隠さない。しかし、IBMメインフレームのハードウェア特性、PL/Iコンパイラの最適化挙動、そしてCICSのストレージ管理機構の三者が交差するこの境界領域こそ、基幹システムの信頼性を左右する最もクリティカルなポイントなのだ。
今回は、PL/Iにおける識別子の柔軟性(予約語を持たないが故のエッジケース)をあえて逆手に取り、`EXEC CICS READ/WRITE` における構造体マッピングの深淵と、モダン化(マイグレーション)を見据えた設計の急所を、アーキテクトの視点から徹底的に紐解いていこう。
—
1. PL/Iの「予約語レス」構造とCICSコマンドの危険な邂逅
PL/Iの言語仕様における最大の特異性は、「PL/Iには真の予約語が存在しない」という点にある。`READ`、`WRITE`、`IF`、`DECLARE`といったキーワードであっても、文脈さえ許せば変数名として定義できてしまう。
/ 良い子は絶対に真似してはいけない危険な変数宣言 /
DCL READ FIXED BIN(31);
DCL CICS CHAR(4);
この仕様は言語としての表現力を飛躍的に高めた一方で、CICSのプリプロセッサ(DFHECI)やPL/Iコンパイラを混乱させる温床となる。特に、マクロ展開や埋め込みSQL、そしてCICSコマンド (`EXEC CICS`) が混在するソースコードにおいて、構造体メンバの名称がCICSのキーワードや内部生成変数と衝突したとき、コンパイラはエラーすら出さずに「意図しないコード」を生成することがある。
CICSオンラインプログラムでは、画面からの入力電文やVSAMからのレコードを格納するために、以下のような大域構造体(BMSマップやレコードレイアウト)を定義するのが定石だ。
Dcl 1 T0100I_AREA based(Ptr_T0100I),
5 FILLER Char(2), / CICS制御領域等 /
5 TRN_CODE Char(4), / トランザクションID /
5 CUST_ID Fixed Bin(31), / 顧客番号(バイナリ) /
5 CUST_NAME Char(30), / 顧客名 /
5 CUST_BAL Dec Fixed(11,2); / 顧客残高(パック10進数) /
この構造体を `EXEC CICS READ` で取得する際、ベース変数 (`based`) とポインタのハンドリングを誤ると、一瞬でストレージ破壊(Storage Violation)を引き起こす。
—
2. アライメントの罠:ハードウェア境界とコンパイラオプション
C言語やC#ではお馴染みの「構造体のパディング(隙間)」だが、PL/Iにおいてもコンパイラのデフォルト動作とハードウェアの仕様を理解していないと痛い目を見る。
S/370およびz/Architectureアーキテクチャでは、2バイトの整数(`FIXED BIN(15)`)は2の倍数のアドレスに、4バイトの整数(`FIXED BIN(31)`)は4の倍数のアドレスに配置されている必要がある(境界アライメント)。もし奇数アドレスなどにこれらが配置された状態でアクセスすると、ハードウェア例外(S0C6アベンド)が発生する。
デフォルト(UNALIGNED)と ALIGNED の違い
PL/Iコンパイラは、デフォルトでは `UNALIGNED`(非整列)でデータを割り付ける。これはストレージの節約にはなるが、CPUのメモリアクセス効率が落ちる。しかし、CICSの通信エリアや外部ファイルのレコードレイアウトを定義する場合、あえて `UNALIGNED` を明示、あるいはデフォルトに依存する必要がある。
/ アーキテクトが推奨する安全な定義手法 /
Dcl 1 ACCOUNT_RECORD Unaligned,
5 ACC_NO Fixed Bin(31), / 4バイト /
5 ACC_TYPE Char(1), / 1バイト(計5バイト目から始まる) /
5 ACC_AMT Dec Fixed(9,2); / 5バイト目からパック10進数配置 /
もしここにうっかり `ALIGNED` が混入すると、`ACC_TYPE` と `ACC_AMT` の間にパディングバイト(ダミーの隙間)が自動挿入され、CICSが外部から受け取った生データ(バイナリイメージ)のオフセットが完全にズレてしまう。VSAMやMQ、他システムとの連携部では、構造体全体に `UNALIGNED` を徹底させることが鉄則である。
—
3. パックデシマル(`DEC FIXED`)の内部符号反転バグとS0C7の恐怖
メインフレームといえばパックデシマル(COMP-3)。PL/Iでは `DEC FIXED (p, q)` として表現される。
CICS経由で画面の3270フィールドから転送されてきたデータや、COBOLプログラムから渡されたデータをPL/Iの `DEC FIXED` 変数で受ける際、最も恐ろしいのが S0C7(Data Exception)アベンド だ。
符号ニブル(Sign Nibble)の破壊
パックデシマルは、1バイトに2桁の数字を格納し、最後の4ビット(ニブル)に符号(`C`, `D`, `F` など)を保持する。
オンライン画面からユーザーが数値を入力し忘れたり、ブランクスペース(X’40’)のまま電文が飛んできたりした場合、PL/I側でその領域を `DEC FIXED` として演算に使用した瞬間、ハードウェアは「有効な符号ではない」と判断し、容赦なくS0C7アベンドを吐いてプロセスを強制終了させる。
【実務での防衛策】
CICSから取得した生データ領域は、一度 `CHAR` 型のバッファで受け、数値演算を行う前に必ず `TESTDEC` 相当のバリデーション、あるいはPL/Iの `VALID` 組み込み関数(Enterprise PL/Iでサポート)を用いて検証するか、ブランクや不正文字をゼロに置き換えるサニタイズ処理を挟まなければならない。
/ 不正データ混入を防ぐための実践的な前処理コード例 /
Dcl Raw_Buffer Char(100) Based(Ptr_Cics_Comm);
/ 領域全体がパックとして有効か事前にチェック(Enterprise PL/I仕様) /
If ^Valid(Raw_Buffer.CUST_BAL) Then Do;
/ エラーログ出力およびCICS画面へのエラーメッセージ返却処理 /
Call Handle_Data_Error();
End;
—
4. ベース変数とポインタによる動的ストレージ操作のエッジケース
CICS環境下では、動的なメモリ獲得は `EXEC CICS GETMAIN` を用いるのが基本だ。取得したストレージの先頭アドレスをPL/Iのポインタ変数に格納し、ベース変数(`BASED` 属性を持つ構造体)を重ね合わせることで、効率的な電文解析が可能になる。
しかしここに、PL/I特有のポインタ管理の罠が潜んでいる。
Dcl Ptr_CommArea Pointer;
Dcl 1 My_CommArea Based(Ptr_CommArea),
5 Request_Id Char(8),
5 Payload_Len Fixed Bin(31),
5 Payload_Data Char(0) Refer(Payload_Len5); / 可変長構造体 /
/ CICSからCOMMAREAのアドレスを取得 /
Ptr_CommArea = Address(EIBTRNID); / 実際にはDFHCOMMAREAのアドレス /
ここで注意すべきは、ポインタ変数のスコープとライフサイクル、そしてストレージの境界違反(S0C4アベンド)である。
CICSのタスクが終了すると、`GETMAIN` で取得した領域は自動解放される(あるいは `EXEC CICS RETURN` で消える)。しかし、解放済みのポインタを指し続けたままベース変数にアクセスしようとすると、OSは容赦なく保護例外(S0C4)を発生させる。
特に、サブプログラム間をポインタや `BYADDR` で構造体を渡して回る設計にしている場合、呼び出し元と呼び出し先のコンパイルオプションの不一致(例えば、一方が `RENT` で一方が非 `RENT`、あるいはストレージレイアウトのズレ)があると、メモリ上の意図しない領域を破壊し、原因特定が極めて困難なバグ(コリプション)に発展する。
—
5. マイグレーション(Java/C#化)を見据えた現代的アーキテクチャの視点
私たちシステムアーキテクトが現在直面している最大のミッションは、これらレガシーなPL/Iプログラムを、Java(Spring Boot)やC#(.NET Core)へと安全に移行(モダナイゼーション)することだ。
移行プロジェクトにおいて、この「PL/I構造体とCICSストレージのマッピング」は最大の難所となる。JavaやC#には、PL/Iの `UNALIGNED` なパックデシマルや、ポインタによる自在なストレージオーバーレイの概念が存在しないためだ。
移行設計における鉄則
1. バイナリレイアウトの明確化:
移行元のPL/Iソースコードから、すべての構造体のオフセット、長さ、データ型の定義をリバースエンジニアリングし、JSONやYAML形式のスキーマ定義(あるいはJavaの `@Struct` / `@Field` アノテーションに相当する定義)へと変換するミドルウェア層を設計する。
2. パックデシマルのエミュレーション:
移行先言語側で、メインフレームの `COMP-3`(Packed Decimal)を正確に再現できる数値演算ライブラリ(あるいはBigDecimalのカスタムフォーマッタ)を必ず採用する。これを怠ると、金額の端数処理や丸め誤差で致命的な監査不整合を引き起こす。
3. CICS通信のREST API化:
`EXEC CICS READ/WRITE` やCOMMAREAのやり取りを、そのままグリッチなバイナリ通信としてJava側に持ってくるのではなく、この機会にOpenAPI(Swagger)ベースのJSON電文へとリファクタリングすることが望ましい。しかし、段階的移行(Strangler Figパターン)をとる場合は、メインフレーム側の通信バッファ構造体をそのままエミュレートするTCP/IPソケット通信層をJava側に実装する必要がある。
—
結びにかえて
PL/Iの構造体とCICSのマッピングは、単なる「データの読み書き」ではない。それはハードウェアの物理制約、コンパイラの最適化ロジック、そしてOSのメモリ管理機構が幾重にも重なり合った、メインフレーム技術の粋を集めた芸術品である。
その挙動を完全に支配し、一分の隙もないコードを書くこと――それこそが、時代を超えて基幹システムを支え続ける私たちシステムスペシャリストのプライドであり、責務なのだ。レガシーの闇を恐れるな。仕様を極め尽くした者だけが、次の世代への確実な橋渡しを成し遂げることができる。
