コンテンツへスキップ

既知の制限

Phasisは、観測可能なすべての仕様テストでV8と同じ動作を目指しています。このページでは、実際にその同一性が保たれない箇所を説明します。多くの場合、ツリーウォーキング方式のPHPインタープリターとJITコンパイル方式のC++エンジンとの根本的な違いが理由です。

パフォーマンスの上限

このエンジンは、機会的にバイトコードVMを使うツリーウォーカーと、数値処理やローカル変数を多用する本体(obj.method()、new Ctor()、this.memberの形式を含む)をネイティブなPHPクロージャに変換するJsToPhpトランスパイラーで構成されています。これらはPHP 8.5のトレーシングJITによってマシンコードにコンパイルできます。ホットなディスパッチの大半はVMのswitchループ内、またはJITコンパイルされたPHP内で直接行われ、ASTを走査して処理するわけではありません。ただし、VMの各オペコードでは依然としてPHPのディスパッチコストが発生し、JsToPhpがまだ対応していない形式は低速なVMにフォールバックします。ディスパッチ律速のワークロードでは、性能の上限はおよそV8の約100倍遅い水準です。

組み込み用途、たとえばPHPデータに対するユーザー提供ロジックの実行、コンテンツ変換パイプライン、検証ルールでは、この上限が問題になることはほとんどありません。V8なら100 ms、Phasisなら10 sですが、どちらもリクエストのタイムアウトを大きく下回ります。差をさらに縮めるには、JsToPhpが扱う形式を増やす(パラメーターの型推論、await / yield*への変換)か、eval()を介さずにopcacheオペコードを直接出力する必要があります。ベンチマーク § ロードマップを参照してください。

マルチエージェントの並行処理

JavaScriptのSharedArrayBuffer + Atomics.wait + postMessageモデルは、ホストが実際のOSスレッドを実行できることを前提とします。PHPではそれができません(すべてのホストで利用できるわけではないpcntl_forkなしでは)。

Phasisは単一のエージェントをシミュレートします。Atomics.waitは直ちに「not-equal」または「timed-out」を返し、notifyは常に0を返します。これにより、単一エージェントで実行されるtest262テスト(入力検証、エラー経路、何もしないnotify)をすべて通過します。これはAtomicsテスト376件中376件に相当します。

互いを観測する2つのエージェントを実際に必要とするテストには対応していません。真の並列処理には、PHPレベルでワーカーキュー(Redis、Beanstalkd、…)を使用してください。

Realmのセマンティクス

ShadowRealmは実装されていますが、各子Realmはホストと同じPHPプロセス内で実行されます。PHPコードを適切に隔離しないと、ホスト関数を通じてRealm間で参照が漏れる可能性があります。これは仕様上許容されていますが、V8は個別のv8::Isolateインスタンスを使って、より厳格に隔離します。

createRealm()(test262のホストフック)は、内部で新しいPhasis\Engineを作成し、標準ライブラリを橋渡しします。

正規表現エンジンの選択

Phasisは、主な正規表現バックエンドとしてPHPのPCRE2を使用し、PCRE2で厳密に処理できないケース(キャプチャ付き後読み、リセットを伴う量指定グループ、一部の/vフラグのセマンティクス)にはカスタムマッチャーを使います。カスタムマッチャーはPCRE2より低速で、ディスパッチ時に互換性のないパターンが検出された場合にのみ使用されます。

特筆すべき影響として、PCRE2の大文字・小文字変換テーブルはV8のICUバージョンと異なる場合があります。特に古いLinuxディストリビューションではその可能性があります(Ubuntu 22.04にはICU 70が含まれますが、Phasisは/iuパターンでホスト環境による差異を上書きするため、ICU 76以降の変換テーブルを同梱しています)。

メモリモデル

PHPの参照カウント方式のGCは、V8のトレーシングコレクターとは異なります。ホストクロージャが捕捉したPHPオブジェクトは、エンジンがそれらのクロージャを保持している間は生存し続け、相互参照の循環はEngineが破棄されるまで回収されません。生存させたくないキャッシュにはWeakMap / WeakRefを使用してください。

process.memoryUsage()や、V8固有の同様のイントロスペクションAPIは実装されていません。

モジュールローダー

ESモジュールローダーはファイルシステムを基準に相対インポートを解決しますが、バンドラーの慣例には対応していません。

  • node_modules解決には対応していません。
  • package.jsonのexportsマップはありません。
  • ファイル拡張子は推測されません(import './foo'ではなくimport './foo.mjs'と記述する必要があります)。
  • HTTPインポートには対応していません。

これはECMAScript仕様の基本形には一致しますが、Nodeの動作とは異なります。独自のモジュールリゾルバー用フックはありません。npmパッケージを使うには、esbuildなどのツールで単一のスクリプトまたはモジュールにバンドルしてから、Phasisに渡してください。

Wasm

WebAssemblyは実装されていません。バインド先となるPHPWasmランタイムがありません。

対応していないブラウザー専用Web API

Phasisには完全なWeb Platform Pack(URL、TextEncoder/TextDecoder、atob/btoa、structuredClone、performance、DOMException)、完全なFetch Pack(fetch、Request、Response、Headers、Body、AbortController/AbortSignal、Blob/File、FormData、EventTarget/Event、WHATWG Streams全機能、navigator)、さらにcrypto(getRandomValues、randomUUID、SubtleCrypto全機能:digest、HMAC、AES-GCM/CBC/CTR、RSA-OAEP/PSS/PKCS1、ECDSA、ECDH、HKDF、PBKDF2)、WebSocket(コンストラクターとRFC 6455フレームコーデック、交換可能なトランスポート)、XMLHttpRequest(fetchトランスポート上に構築)、および実際のイベントループ(setTimeout、setInterval、clearTimeout、clearInterval、queueMicrotask、AsyncContext)が含まれます。Web APIを参照してください。

含まれないもの:

  • window、document、DOMツリー。Phasisはブラウザーではありません。
  • localStorage、sessionStorage、IndexedDB。ホスト側の責任範囲であり、永続化は埋め込み側が担います。
  • Worker、SharedWorker、MessageChannel。実際のOSスレッドが必要ですが、PHPにはありません。
  • Service Workers、Push API、Notifications、BroadcastChannel。ブラウザー以外では用途がありません。

Nodeのグローバル変数

process、Buffer、__dirname、__filename、require()も、どれも存在しません。これらはNodeホストの慣例であり、言語機能ではありません。

Nodeのコードを移植する場合は、require('foo')をimport 'foo'に置き換え、process.env.Xをホストにバインドされたenv.Xに差し替え、PHPから__dirname相当のパスを渡してください。

小数の精度

ECMAScriptの数値はIEEE 754倍精度浮動小数点数です。有効桁数が15桁を超える十進演算では、ほかのすべてのJSエンジンと同様に精度が失われます。整数を正確に扱うにはBigIntを使い、端数も正確に扱う必要がある場合はJSの十進数ライブラリを使ってください。

PHPのbcmathはBigIntに内部で使われます。V8との実質的な違いは、非常に大きなbigint演算が、V8のC++実装よりPHPでは遅いことです。数値の正確さは同一です。