7月3日、Thom Brownが「LOAD "PL/CBMBASIC",8,1: Commodore 64 BASIC for PostgreSQL」と題した記事を公開した。PostgreSQLのストアドプロシージャとして、1982年製のCommodore 64 BASIC V2を動作させる拡張「PL/CBMBASIC」を詳しく解説した内容だ。
38911 BASIC BYTES FREE。Commodore 64(C64)は1982年に発売された8ビットホームコンピュータで、当時世界で最も売れたPCの一つだ。青い画面に白い文字、雑誌から数ページ打ち込んだプログラム、?SYNTAX ERROR IN 2340のメッセージ、そして大陸移動より遅いフロッピードライブ。それが今、PostgreSQLの中で動く。
PL/CBMBASIC とは何か
PL/CBMBASIC は、PostgreSQLの手続き型言語拡張(PL)機構を利用して、関数ボディをCommodore 64 BASIC V2で記述・実行できるようにするものだ。PostgreSQLはPL/PythonやPL/Rubyのようにサードパーティ言語をストアドプロシージャとして組み込める仕組みを標準で持っており、PL/CBMBASICはその仕組みを使ってC64 BASICインタープリタを内部に組み込んでいる。
「似せた処理系」ではない点が重要だ。Michael Steilの cbmbasic プロジェクトが6502 ROMを静的にCへ再コンパイルしたコードを使い、1982年当時の実際のMicrosoft/Commodoreインタープリタをそのままshared libraryとして取り込んでいる。
使い方はこうだ:
CREATE EXTENSION plcbmbasic;
CREATE FUNCTION hello(who text) RETURNS text AS $$
10 PRINT "HELLO, ";WHO$;"!"
$$ LANGUAGE plcbmbasic;
SELECT hello('WORLD'); -- HELLO, WORLD!
行番号は必須だ。0〜9行は予約済みで、拡張が関数引数をBASICの変数として自動注入する領域として使われる。text型の引数whoはWHO$に、smallint型のlivesは16ビット整数LIVES%に、それ以外の数値は40ビットのCBM浮動小数点(有効数字9桁)に変換される。
関数呼び出しのたびに64KBのRAM配列をゼロクリアし、CPUレジスタをリセットして$E394からROMへ再エントリーする「インメモリ電源投入」が行われる。このコストは約15〜20マイクロ秒で、実機の約1000倍速い。大きなテーブルの行ごとに呼び出しても現実的な速度だ。
バリデータが「BASIC V2の怒り」を再現する
BASIC V2のトークナイザはキーワードを識別子の中にも見つけてしまうという特性がある。TOTALにはTOが含まれ、SCOREにはORが、BUDGETにはGETが潜んでいる。また変数名は先頭2文字しか区別されないため、ALPHAとALPSは同じ変数として扱われる。
PL/CBMBASICのバリデータはこれらをCREATE FUNCTIONの時点で検出する:
ERROR: parameter name "total" contains the BASIC keyword TO
HINT: This is why nobody could ever have a variable called TOTAL
on the Commodore 64.
エラーメッセージにまでC64魂が込められている。
最大の見どころ:「デバイス8はデータベース」
C64においてデータはフロッピードライブ(デバイス8)に住んでいた。PL/CBMBASICでは、デバイス8がデータベースそのものになる。OPENに渡す「ファイル名」がSQL文で、トランザクション内のSPI(Server Programming Interface)として実行される:
CREATE FUNCTION top_scores() RETURNS text AS $$
10 OPEN 1,8,0,"SELECT NAME, SCORE FROM HISCORES ORDER BY SCORE DESC"
20 INPUT#1,N$,S
30 IF ST<>0 AND N$="" THEN 60
40 PRINT N$;" ";S
50 IF ST=0 THEN 20
60 CLOSE 1
$$ LANGUAGE plcbmbasic;
カラムの値はCR区切りで1レコードずつ返され、EOF時にはSTにビット64が立つ。1985年に1541ドライブ相手に書いたループがそのまま動く構造だ。
セカンダリアドレス15(C64ではドライブのコマンドチャンネル)もサポートされている:
10 OPEN 15,8,15
20 PRINT#15,"DELETE FROM HISCORES WHERE SCORE < 1000"
30 INPUT#15,EN,EM$,RC,ES
ステータスレコードは0,OK,<rows>,0で返る。PRINT#で255文字を超えるSQL文を分割して送ることも可能だ。なお、これはスーパーユーザー専用の「untrusted language」なので、FORループと大文字だけで好き放題できる。
パフォーマンス比較
PL/Pythonと比較したベンチマーク結果は以下のとおりだ:
- 単純な呼び出し:PL/Pythonが約14〜19倍速い
- 関数内で100行クエリする処理:PL/Pythonが約6倍速い(双方ともSPIコストが支配的になるため差が縮まる)
PL/Pythonはインタープリタを温存するのに対し、PL/CBMBASICは呼び出しのたびにC64を「起動」する。それでもBASICインタープリタは毎秒約100万ステートメントを処理する。実機の約1000ステートメント/秒から1000倍の高速化だ。
また、10 GOTO 10の無限ループはstatement_timeoutで正常終了する。CHECK_FOR_INTERRUPTS()がKERNALのSTOP処理にパッチされているためで、記事は「RUN/STOPキーがついに確実に動くようになった。44年しかかからなかった」と記している。
主な制限事項
- 文字列はすべて大文字(C64のデフォルト文字セットに小文字が存在しないため)
- 文字列の最大長は255文字
- NULLは空文字列として扱われる
INPUTはエラー(キーボードがないため)POKEとSYSはエミュレートされた64KB内で動作するが、影響は次の電源投入サイクルまで持続する
本番環境へのデプロイについて、著者は「…それは選択肢の一つだろう。ノスタルジアは必須だ」と締めくくっている。
詳細はLOAD "PL/CBMBASIC",8,1: Commodore 64 BASIC for PostgreSQLを参照していただきたい。