【実務・中級編】TRANSLATE関数による文字変換テーブルのメモリ配置 – PL/Iの基本構文とデータ制御実践ガイド

メインフレームの心臓部を叩く:TRANSLATE関数と256バイト変換テーブルの深層

おい、調子はどうだ?
今日もどこかのジョブがスプールでアベンドして、夜間バッチのリカバリに走り回っているところか?それとも、オープン系へのマイグレーションに向けて、20年物の真っ黒なPL/Iソースコードの解析に頭を悩ませているところか。

どちらにしても、よくぞこの扉を叩いてくれた。
今日は、PL/Iという言語が持つ「古くて新しい、しかしハードウェアの限界まで性能を引き出すための魔術」について話をしよう。テーマは 「TRANSLATE関数による文字変換テーブルのメモリ配置」 だ。

C言語やJavaしか触ったことがない若い連中からすると、「たかが文字置換ごとなぜそんなに大げさに?」と思われるかもしれない。だが、z/Architecture(S/390系)のメインフレームの世界では、この「256バイトのテーブル」の作り方とメモリ上の配置いかんによって、月間数千万件を処理する巨大な定常バッチのランタイムが、数十分も変わってくるんだ。

今回は、PL/Iの構文規則、そしてハードウェア命令(TR命令)とコンパイラの挙動がどう結びついているのか、俺の現場の経験を総動員して叩き込んでやる。心してついてこい。

—

1. 予約語を持たないPL/Iと「識別子」の魔力

まず前提として、PL/Iの根幹を成す思想に触れておこう。
他の言語、例えばCOBOLやPL/Iの親戚であるような言語と違い、PL/Iには厳密な「予約語(Reserved Words)」が存在しない。

これが何を意味するか分かるか?
なんと、コンテキスト(文脈)によっては、言語のキーワード(`IF` や `DECLARE` など)すら、プログラマが変数名や配列名として定義できてしまうのだ。

/ 狂気のようだが、PL/Iではこれが文法上エラーにならない /
DCL IF FIXED BIN(31);
DCL THEN CHARACTER(10);

IF = 10IF; / コンパイラは文脈からキーワードと変数を完璧に識別する /

コンパイラは、その識別子が宣言された文脈(Declaration)と、使用されている位置(Context)を解析して意味を決定する。この「コンテキスト依存の字句解析」は、時として人間側を大いに混乱させるが、同時に言語仕様の拡張性を担保してきた。

今回のテーマである `TRANSLATE` も、ビルトイン関数(BUILTIN)として扱われる。もし君がうっかり同じ名前の変数をグローバルスコープで宣言しようものなら、コンパイラは血眼になって警告(Warning)を吐き出すか、意図しない名前解決を行ってバグの温床を作る。
だからこそ、システムアーキテクトとしての第一歩は、ビルトイン関数名やハードウェアに直結する制御ブロックの名前を汚染しない、クリーンなコーディング標準を遵守することから始まるのだ。

—

2. TR命令と256バイト変換テーブルの正体

メインフレームのCPU(z/Architecture)には、文字列を高速に変換するための専用機械語命令が存在する。それが TR(Translate)命令 だ。

TR命令の仕組みは極めてシンプルかつ強力だ。
1. 変換対象の文字列領域(最大256バイト、複数指定も可)をスキャンする。
2. 各文字の持つバイナリ値(EBCDICコードの0x00〜0xFF)を、256バイトの変換テーブル(Translate Table)のオフセット(インデックス)として使用する。
3. テーブルのその位置に格納されている「新しいバイト値」で元の文字を置き換える。

このハードウェア命令を、PL/Iの `TRANSLATE` ビルトイン関数はダイレクトに呼び出す。

/ 標準的なTRANSLATEの構文 /
OUT_STRING = TRANSLATE(IN_STRING, TABLE_STRING);

ここで問題になるのが、第2引数である `TABLE_STRING`(変換テーブル)のメモリ配置と静的領域の確保 だ。
もし、この256バイトのテーブルを毎回のループやサブルーチン呼び出しのたびに動的に生成(WORKING-STORAGEや自動ストレージ上に構築)していたらどうなるか? 無駄なCPUサイクルが消費され、メインフレームの最大の武器である「スループットの高さ」が台無しになる。

したがって、実務の現場では、この256バイトの変換テーブルは `STATIC INITIAL` 属性 を使って静的領域に完全に固定配置し、さらにはCPUのキャッシュヒット率を考慮したアライメントを意識する必要があるのだ。

—

3. 実践:VSAM入出力と連動する高速文字変換バッチ

百聞は一見にしかずだ。
実際に現場の商用プログラムで使用するレベルにまで落とし込んだ、堅牢なPL/Iのコード例を見せよう。

このプログラムは、VSAM(KSDS)からレコードを読み込み、社内独自のレガシー文字コード(JIS系や特殊EBCDIC)から標準EBCDICへの高速変換を `TRANSLATE` で行い、別のファイルへ書き出すバッチの骨子だ。

  • モジュール名: TRCONV01
  • 概要: VSAM入力レコードの高速文字変換処理(TR命令活用版)

————————————————
TRCONV01: PROC OPTIONS(MAIN);

/ ビルトイン関数の明示的宣言(コンパイラの最適化を引き出す) /
DCL TRANSLATE BUILTIN;
DCL LENGTH BUILTIN;

/ VSAM入出力用ファイル定義 /
DCL INFILE FILE RECORD INPUT ENV(VSAM);
DCL OUTFILE FILE RECORD OUTPUT;

/ レコード領域の定義 /
DCL 1 IN_RECORD,
5 IN_KEY CHAR(8),
5 IN_BODY CHAR(248);

DCL 1 OUT_RECORD,
5 OUT_KEY CHAR(8),
5 OUT_BODY CHAR(248);

DCL IO_STATUS FIXED BIN(31) INIT(0);

/ =============================================================== /
/ 【核心】256バイト変換テーブルの静的領域確保 /
/ STATIC 属性により、ロードモジュール内に定数として配置され、 /
/ プログラム起動時に一度だけメモリ上にロードされる(再入可能性考慮)/
/ =============================================================== /
DCL CONV_TABLE CHAR(256) STATIC INITIAL (
/ 0x00 – 0x0F /
X’000102030405060708090A0B0C0D0E0F’,
/ 0x10 – 0x1F (例として一部を安全なマッピングに置換) /
X’101112131415161718191A1B1C1D1E1F’,
/ 中略(実際には256バイト分の16進数リテラルを記述) /
/ … ここでは便宜上、初期化のパターンを示す … /
/ 最後のブロック (0xF0 – 0xFF) /
X’F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF’
);

/ ファイルオープン /
OPEN FILE(INFILE), FILE(OUTFILE);

/ メイン処理ループ /
DO FOREVER;

/ VSAM KSDSからの順次読み込み (READ NEXT) /
READ FILE(INFILE) INTO(IN_RECORD);

IF @@EOF_CONDITION THEN LEAVE; / 実際にはON ENDFILEを使用 /

/ =========================================================== /
/ 高速文字変換の実行 /
/ 248バイトのボディ部に対してTR命令が一発で炸裂する /
/ =========================================================== /
OUT_KEY = IN_KEY; / キー部はそのまま /
OUT_BODY = TRANSLATE(IN_BODY, CONV_TABLE);

/ 出力ファイルへの書き込み /
WRITE FILE(OUTFILE) FROM(OUT_RECORD);

END;

/ ファイルクローズ /
CLOSE FILE(INFILE), FILE(OUTFILE);

RETURN;

END TRCONV01;

—

4. アーキテクトが教える、実務上の「罠」とデバッグのコツ

このコード、一見すると完璧に見えるだろう。だが、レガシーシステムの移行や保守の現場では、これだけでは足元をすくわれる落とし穴がある。先輩として、いくつか実戦で役立つ知見を授けておこう。

① 再入可能性(Reentrancy / 属性 `REENTRANT`)の考慮

もしこのプログラムが CICS のオンライン画面トランザクションから呼び出される、あるいはマルチタスク(`ATTACH` マクロなど)環境で非同期に複数走るバッチの一部である場合、`STATIC INITIAL` で持たせたテーブルが意図せず書き換えられるようなことがあってはならない(もちろん `CHAR(256)` の定数はイミュータブルとして扱われるべきだが)。
コンパイラオプションで `RENT` を指定してコンパイルする際、テーブル領域がセクションとして正しく Read-Only(RO)属性の領域に落ちているか、リンクエディットマップ(Load Map)を必ず確認しろ。RW(Read-Write)領域に落ちていると、ストレージ破壊バグの温床になる。

② 256バイトの整合性とエディタの文字コード問題

モダンなPC上の開発環境( EclipseベースのIDzなど)でこのソースコードを保守するとき、ソースファイル自体の文字コード(CP930やUTF-8など)に注意しろ。
`X’…’` 形式の16進数リテラルでテーブルを定義していれば安全だが、もし文字リテラルでテーブルを記述していると、ホスト(EBCDIC)とPC(ASCII/Shift-JIS)間の転送時にコードページ変換が走り、夜間バッチが走り出した瞬間にデータがグジャグジャになる という、メインフレームエンジニアなら誰もが一度は通る悪夢を見るハメになる。
テーブル定義は必ず 16進数リテラル(`X’…’`)で記述するのが、百戦錬磨のプロの鉄則だ。

—

おわりに

PL/Iは古い言語だと言う奴がいる。だが、ハードウェアの構造をここまでダイレクトに、かつ美しく表現できる言語は、現代のプログラミング言語広しといえどもそうそうない。

`TRANSLATE` 関数と256バイトの静的変換テーブル。
この背後にあるハードウェア(TR命令)の息吹を感じながらコードを書けるようになった時、君はもう「ただコードを直すだけのプログラマ」ではなく、「メインフレームの限界を引き出すシステムアーキテクト」への階段を確実に上っているはずだ。

さあ、コーヒーブレイクはここまでだ。
次のリリースに向けたテストJCLの確認に戻るとしよう。何か詰まったら、いつでも俺のところに聞きに来い。

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