已知限制
Phasis 致力于在所有可观测的规范测试中与 V8 表现一致。本页列出了这种一致性在实践中的局限,通常源于树遍历 PHP 解释器与 JIT 编译的 C++ 引擎之间存在根本差异。
性能上限
该引擎采用树遍历方式,搭配机会式字节码 VM 和 JsToPhp 转译器;后者会将数值密集型 / 局部变量密集型主体(包括 obj.method()、new Ctor() 和 this.member 形态)降级转换为原生 PHP 闭包,供 PHP 8.5 的追踪式 JIT 编译为机器码。大多数热点分派都在 VM 的 switch 循环中或直接在 JIT 编译的 PHP 内完成,而非通过遍历 AST 完成;但每条 VM 操作码仍要承担 PHP 分派开销,而 JsToPhp 尚未处理的任何形态都会回退到较慢的 VM。受分派开销限制的工作负载,其性能上限大约比 V8 慢 ~100×。
对于嵌入式工作负载,例如在 PHP 数据上运行用户提供的逻辑、内容转换流水线和验证规则,性能上限很少会造成影响。V8 耗时 100 毫秒;Phasis 耗时 10 秒;两者都远低于请求超时。要进一步缩小差距,需要让 JsToPhp 支持更多形态(参数类型推断、await / yield* 降级转换),或直接生成 opcache 操作码,而不是通过 eval() 生成。参阅基准测试 § 路线图。
多代理并发
JavaScript 的 SharedArrayBuffer + Atomics.wait + postMessage 模型假设宿主可以运行真正的 OS 线程。PHP 无法做到这一点(除非使用 pcntl_fork,但并非所有宿主都提供该功能)。
Phasis 会模拟单个代理 — Atomics.wait 会立即返回 "not-equal" 或 "timed-out",notify 始终返回 0。这能通过所有以单个代理运行的 test262 测试(输入验证、错误路径、空操作 notify),涵盖 376 Atomics 测试中的 376。
不支持实际需要两个并发代理相互观察的测试。要实现真正的并行,请在 PHP 层级使用工作线程队列(Redis、Beanstalkd、…)。
Realm 语义
ShadowRealm 已实现,但每个子 realm 都与宿主运行在同一个 PHP 进程中。宿主函数可能会让引用跨 realm 泄漏,因此请谨慎隔离 PHP 代码。规范允许这种情况;V8 会通过单独的 v8::Isolate 实例实施更严格的隔离。
createRealm()(test262 宿主钩子)会在内部创建新的 Phasis\Engine,并桥接标准库。
正则表达式引擎选择
Phasis 使用 PHP 的 PCRE2 作为主要正则表达式后端,并为 PCRE2 无法精确处理的情况提供自定义匹配器(带捕获的后行断言、带重置的量词组、某些 /v 标志语义)。自定义匹配器比 PCRE2 慢,仅在分派逻辑检测到不兼容模式时使用。
值得注意的后果是:PCRE2 的大小写折叠表可能与 V8 的 ICU 版本不同,尤其是在较旧的 Linux 发行版上(Ubuntu 22.04 自带 ICU 70;Phasis 捆绑了 ICU 76+ 折叠表,以覆盖宿主版本差异对 /iu 模式的影响)。
内存模型
PHP 的引用计数 GC 与 V8 的追踪式垃圾回收器不同。由宿主闭包捕获的 PHP 对象只要仍被引擎持有,这些闭包就会一直存活;它们之间的循环引用要等到销毁 Engine 后才会被回收。对于不希望一直保留的缓存,请使用 WeakMap / WeakRef。
process.memoryUsage() 以及类似的 V8 专属内省 API 尚未实现。
模块加载器
ES 模块加载器会根据文件系统解析相对导入,但不支持打包器约定:
- 不支持
node_modules解析。 - 不支持
package.jsonexports映射。 - 不推断文件扩展名(必须写
import './foo.mjs',而不是import './foo')。 - 不支持 HTTP 导入。
这符合纯 ECMAScript 规范,但不符合 Node 的行为。没有用于自定义模块解析器的钩子。要使用 npm 包,请先用 esbuild 等工具将其打包成单个脚本或模块,再传给 Phasis。
Wasm
WebAssembly 尚未实现。没有可供绑定的 PHP wasm 运行时。
跳过的仅限浏览器 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 双精度浮点数。与所有其他 JS 引擎一样,进行有效位数超过 15 位的十进制运算会丢失精度。需要精确整数运算时请使用 BigInt;需要精确的小数运算时,请使用 JS 十进制库。
PHP 的 bcmath 在内部用于 BigInt;与 V8 的唯一实际差异是,大数值的 bigint 运算在 PHP 中比在 V8 的 C++ 实现中更慢。数值结果完全一致。