制御フローの深淵:DO WHILEとDO UNTILが分かつ「境界条件」の哲学
メインフレームの現場で何十年と生き抜いてきた諸君なら、一度は「なぜ初回ループが回らないのか(あるいは回りすぎるのか)」というシンプルなバグに、夜中のバッチ実行中に冷や汗をかいた経験があるはずだ。
PL/Iにおける `DO WHILE` と `DO UNTIL`。この二つは単なるループのバリエーションではない。基幹システムの信頼性を担保する上での「境界条件の設計思想」そのものである。今日は、マイグレーションの現場で最も頻出するこの判定タイミングの差異を、コンパイラ最適化やメモリ管理の視点から紐解いていこう。
—
1. 判定タイミングの「論理的断絶」
まず、最も基本的な仕様を確認する。と言っても、マニュアルの再確認ではない。「どのタイミングでレジスタが書き換わるか」という視点だ。
- DO WHILE: ループの「入り口」で判定を行う。条件が偽であれば、ループ内の命令は一度も実行されない。
- DO UNTIL: ループの「出口」で判定を行う。つまり、最低でも一度はブロック内の命令が実行されることを保証する。
/ 典型的なバッチ処理の例 /
DCL I FIXED BIN(15) INIT(0);
/ WHILEの場合:I=0の時点で条件不適合なら即座にスキップ /
DO WHILE(I > 0);
CALL PROCESS_DATA; / この行は到達不能コードとなる /
END;
/ UNTILの場合:必ず一度は処理を走らせる /
DO UNTIL(I > 0);
CALL PROCESS_DATA; / 初回は無条件で実行される /
END;
なぜこれが重要か? それは、「データの初期状態が不定」なケースにおいて、`DO UNTIL` を不用意に使うと致命的なABEND(S0C4やS0C7)を招くからだ。
—
2. マイグレーションにおける「地雷」:ポインタと動的メモリ
JavaやC#への移行を検討しているアーキテクト諸君に忠告したい。PL/Iの `BASED` 変数と `POINTER` を駆使した動的メモリ操作は、`DO UNTIL` との相性が非常に悪い。
例えば、CICSの通信領域(COMMAREA)から動的に `ALLOCATE` した領域を走査する際、`DO UNTIL` を使うと、ポインタが `NULL` であることをチェックする前に参照(デリファレンス)を行ってしまう可能性がある。
/ 悪い例:ポインタがNULLの場合、UNTILだと判定前にアクセスしてしまう /
DCL PTR_DATA POINTER;
DCL BASED_VAR CHAR(80) BASED(PTR_DATA);
DO UNTIL(PTR_DATA = NULL());
/ ここでABEND S0C4が発生するリスクがある /
IF BASED_VAR = ‘END’ THEN LEAVE;
PTR_DATA = NEXT_PTR(PTR_DATA);
END;
これを `DO WHILE` で書き換えるだけで、セーフティネットが一枚増える。レガシーコードを現代言語へ移植する際、ロジックを直訳するのではなく、「なぜこのループ構造なのか」という設計意図を解析しなければならない。その際、この「判定タイミング」は最も重要なヒントになる。
—
3. パックデシマルとコンパイラ最適化の魔窟
基幹系で避けられないのが `FIXED DECIMAL`(パックデシマル)の扱いだ。
古いコンパイラオプション(`OPTIMIZE(2)` など)を適用した際、ループ条件判定の直前でレジスタへパックデシマルをロードする命令が省略され、古い値がレジスタに残ったまま判定が行われるという、極めて稀なコンパイラバグ(あるいは仕様)に遭遇したことがある。
特に `DO UNTIL` の場合、ループ末尾での演算結果が正しくメインメモリに書き戻される前に判定が走ると、符号反転バグ(`S0C7`)が誘発される。
トラブルシューティングの勘所
もし君たちが解析中のバッチで「原因不明のループ暴走」や「符号異常」に悩まされているなら、以下の点を確認してほしい。
1. コンパイラリストの生成: `LIST` オプションで生成されたアセンブラコードを確認し、判定直前の `PACK` または `UNPK` 命令が意図通りに発行されているか。
2. `VOLATILE` 属性の検討: 最適化によってレジスタに保持されすぎている変数をメモリへ強制的に同期させるために、`VOLATILE` キーワードを付与して挙動が変わるかテストする。
—
結論:アーキテクトとしての矜持
`DO WHILE` は「守り」の構造であり、`DO UNTIL` は「攻め」の構造である。
基幹システムの保守・移行において、我々が目指すべきは「美しいコード」ではなく「壊れないコード」だ。
マイグレーション先のJavaで `while` や `do-while` に置き換える際、単に構文を変換するだけではいけない。PL/Iが抱えていた「メモリの直接制御」と「判定タイミングによる副作用」が、新しいプラットフォームでどのように抽象化され、隠蔽されるのか。そこまで見通して初めて、我々は「アーキテクト」と名乗れるのだ。
次のリリースで、君たちが書いたコードが何十年先もメインフレームの魂を背負って走り続けることを期待している。もし何かに詰まったら、アセンブラのレベルまで降りて行け。機械は嘘をつかない。嘘をつくのは、いつだって人間の方なのだから。
