7月5日、Ekin Ertacが「I Don't Know Rust. My AI Is Rewriting PHP in It Anyway.」と題した記事を公開した。この記事では、Rustを一切知らない開発者がAIを使ってPHPインタープリターをゼロから実装し、PHP公式の2万2千件のテストで検証するという実験について詳しく紹介されている。
Rustを書けない人間が、Rustで書かれたPHPエンジンをShipした
Ekin Ertacは自分でこう書いている。「Rustを書いたことがない。レキサーを作ったことがない。ツリーウォーキングエバリュエーター(構文木を直接解釈して実行する方式)が何かを、Wikipediaを見ずに説明できない」。
それでも彼のターミナルには、Rustで書かれたPHPエンジンがWordPressのフロントページを出力した記録が残っている。タイトルは<title>Phargo Test Site</title>、SQLiteから引いた"Hello world!"、末尾の</html>——完全に動作するWordPressページだ。そのエンジンPhargoには、PHPで書かれた実装コードが一行も存在しない(実装言語はRustのみだ)。
コードを書いたのはAIだ。Ertacの役割は「狙いを定めること」と、現代ソフトウェア開発で最も強力なフレーズを打ち込むことだった——「_looks good, continue._」
「AIに自分の宿題を採点させない」という原則
この実験のコアにあるのは一つのアイデアだ。AIが書いたコードを、AIに評価させてはいけない。
PHPには公式のテストスイートが存在する。PHPインターナルチームが30年かけて書いた**約2万2千本の.phptファイル**だ。.phptはPHPの公式テスト形式で、入力スクリプト・期待出力・実行条件をひとつのテキストファイルに記述する。テストスイート本体はPHPの公式リポジトリで管理されており、DateTimeの夏時間計算から、var_dump()が浮動小数点数をどう出力するかまで、言語の「呪われた隅」を網羅している。Ertacはこれをオラクル(判定基準)として採用し、毎回の変更後にスコアをリポジトリに自動生成する仕組みを構築した。
「その数字は、おだてても、交渉しても、プロンプトで気分を良くさせることもできない。
bug40261.phptが通るか通らないか、それだけだ」
現在のスコアは**22,037件中3,844件——17.4%。数字だけ見れば低いが、残りの大半はCエクステンション(GD、curl、MySQL等)を対象とするテストで、そもそもスコープ外だ。現実的な上限は40〜45%**とErtacは見積もっており、ゼロからのスタートとしては着実な前進だ。
Ertacの作業ループはシンプルだ:
- AIが失敗テストのヒストグラムを分析し、修正できる最大のクラスターを特定
- AIが実装
- 約7分かけて2万2千件のテストを実行
- スコアが上がったらコミット・プッシュ
- スコアが下がったら「regressed、見直して」と一言
テストハーネス自体が嘘をついていた
開発途中、パス率が不自然に頭打ちになる時期があった。明らかにシンプルなテストが、期待値と出力が一致しているように見えるのに落ち続ける。
原因はキャリッジリターン(\r\n)だった。テストコーパスをWindowsでチェックアウトしたためCRLFになっており、Phargoのスコアボードがバイト単位で比較していた一方、PHP公式のテストランナーは比較前に改行コードを正規化していた。つまり数週間にわたって、複数行テストのほぼ全件がサイレントに落ちていた。
正規化コード一行の追加で、数百件のテストが一気にグリーンに変わった。
Ertacはここから「測定器を測れ」という教訓を得た。スコアが不審な頭打ちを見せるたびに最初に問うべき問いは「エンジンが間違っているのか、スコアボードが嘘をついているのか」だ。
実行してみて初めて発覚した「動いているふりをしていた機能」
テストコーパスが暴いたバグの中でも特に興味深いのが、「存在していて、パースされて、エラーも出ず、しかし何もしていなかった」機能群だ:
clone— パースはされていたが評価結果がNULL。DateTimeImmutableの日付演算が全滅していた(immutableな日付処理はcloneに依存しているため)unset($arr[$key])— 完全なno-op。キーが消えなかったtrim($str, $charlist)— charlist引数を無視して常に空白をトリム$$variable(可変変数) — 未実装static関数変数 — 未実装spl_autoload_register()— オートローダーを受け取って、一度も呼ばなかったcatch (\Throwable)— 何にもマッチしなかった(catch-allとして)
デモでは全部生き残る。コードレビューでも(Rustが読めない人間がレビューするなら)生き残る。2万2千件のテストは一つも見逃さなかった。
WordPressが動いた
最終目標はWordPressの動作だった。WordPressは2003年から続くPHPの全イディオムが堆積した「最終ボス」的コードベースだ。
wp-load.phpのブートストラップだけでも、goto(WordPressのHTMLパーサーが使っている)、str_replaceの参照渡し$countパラメータ、正規表現文字クラス内の\xNNエスケープ、function_exists()が半数のビルトインを認識しない問題など、一連のブロッカーを突破する必要があった。
結果として現在の状況は:
- ✅ フレッシュインストール完了、フロントページがデータベースから描画される
- ✅
/wp-admin/も描画される(ダッシュボードが動いたことの方がフロントページより驚いたとErtacは書いている) - ⚠️ REST APIは未検証
- ⚠️ 現時点では本物のPHPより約55倍遅い(7.1秒 vs 126ミリ秒)——ただし新しいバイトコードVMのマイクロベンチマークではPHP 8.5の1〜3倍の速度が出ており、改善中
パフォーマンスに関して補足すると、Phargoのアーキテクチャはツリーウォーキングエバリュエーター(構文木を直接解釈する方式)から始まり、現在はバイトコードVMへの移行が進んでいる。ツリーウォーキング方式は実装が単純な反面、ノードのトラバーサルオーバーヘッドが大きく低速になりがちで、多くのインタープリターが最終的にバイトコードVMへ移行する。PhargoがWordPress上で示している55倍の遅延はこの段階に起因しており、VMへの移行が完了すれば大幅な改善が見込まれる。
この実験が示すもの
Ertacはこう結論づける。「AIがレキサーを書けるかどうかは最初から問題ではなかった。本当の問いは、コードを検証できない人間がどうプロジェクトを誠実に保つか、だった」。
答えは古典的だ——他人が書いたテスト、現実が動いた時だけ動く数字、結果が良くても悪くも公開リポジトリにプッシュすること。
エンジンは現在約2万4千行のRustになった。Ertacはまだ一行も読めない。
詳細はI Don't Know Rust. My AI Is Rewriting PHP in It Anyway.を参照していただきたい。