跳转到正文

已知限制

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.json exports 映射。
  • 不推断文件扩展名(必须写 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++ 实现中更慢。数值结果完全一致。