Перейти к содержимому

Известные ограничения

Phasis стремится идентично V8 проходить все наблюдаемые тесты спецификаций. На этой странице описаны ситуации, когда на практике это сходство заканчивается — обычно из-за принципиальных различий между интерпретатором PHP с обходом дерева и движком на C++ с JIT-компиляцией.

Предел производительности

Движок обходит дерево, использует виртуальную машину байт-кода при подходящих условиях и транспилятор JsToPhp, преобразующий тела с преобладанием чисел и локальных переменных (включая формы obj.method(), new Ctor() и this.member) в собственные замыкания PHP, которые трассирующий JIT-компилятор PHP 8.5 может скомпилировать в машинный код. Большинство интенсивных операций выполняется внутри цикла switch виртуальной машины или непосредственно в замыканиях PHP, скомпилированных JIT, а не обходом AST; однако каждая инструкция виртуальной машины по-прежнему облагается издержками диспетчеризации PHP, а любые конструкции, которые JsToPhp пока не поддерживает, выполняются на более медленной виртуальной машине. Для задач, ограниченных диспетчеризацией, производительность примерно в 100 раз ниже, чем у V8.

Для встраиваемых задач — выполнения пользовательской логики над данными PHP, конвейеров преобразования контента, правил проверки — верхний предел редко имеет значение. V8 — это 100 мс; Phasis — 10 с; оба значения намного ниже тайм-аута запроса. Чтобы сократить разрыв, пришлось бы научить JsToPhp обрабатывать больше форм (вывод типов параметров, преобразование await / yield*) или генерировать опкоды opcache напрямую, а не через eval(). См. Тесты производительности § План развития.

Параллелизм нескольких агентов

Модель JavaScript: SharedArrayBuffer + Atomics.wait + postMessage предполагает, что хост умеет запускать настоящие потоки ОС. PHP этого не умеет (без pcntl_fork, который доступен не на каждом хосте).

Phasis имитирует одного агента: Atomics.wait сразу возвращает «not-equal» или «timed-out», а notify всегда возвращает 0. Это позволяет пройти все тесты test262 для одного агента (проверка входных данных, обработка ошибок, пустой вызов notify), что охватывает 376 из 376 тестов Atomics.

Тесты, которым действительно нужны два взаимодействующих друг с другом агента, не поддерживаются. Для настоящего параллелизма используйте очередь задач на уровне PHP (Redis, Beanstalkd, …).

Семантика Realm

ShadowRealm реализован, но каждый дочерний Realm работает в том же процессе PHP, что и хост. Функции хоста могут передавать ссылки между Realm, если не изолировать код PHP. Спецификация это допускает; V8 обеспечивает более строгую изоляцию с помощью отдельных экземпляров v8::Isolate.

createRealm() (хук хоста test262) создает новый экземпляр Phasis\Engine внутри и связывает со стандартной библиотекой.

Выбор движка регулярных выражений

Phasis использует PCRE2 из PHP как основной движок регулярных выражений и собственный механизм сопоставления для случаев, которые PCRE2 не может обработать точно (просмотр назад с захватами, квантифицированные группы со сбросом, некоторые варианты семантики флага /v). Собственный механизм медленнее PCRE2 и используется только тогда, когда диспетчеризация обнаруживает несовместимый шаблон.

Важное следствие: таблица приведения к нижнему регистру PCRE2 может отличаться от таблицы версии ICU в V8, особенно в старых дистрибутивах Linux (Ubuntu 22.04 поставляется с ICU 70; Phasis включает таблицу преобразования регистра ICU 76+, чтобы перекрыть различия в окружении хоста для шаблонов /iu).

Модель памяти

Сборщик мусора со счетчиком ссылок в PHP отличается от трассирующего сборщика в V8. Объекты PHP, захваченные замыканиями хоста, остаются в памяти, пока движок хранит эти замыкания; циклические ссылки между ними не удаляются, пока не будет уничтожен Engine. Используйте WeakMap / WeakRef для кэшей, которые не должны удерживать объекты в памяти.

process.memoryUsage() и подобные API интроспекции, специфичные для V8, не реализованы.

Загрузчик модулей

Загрузчик модулей ES разрешает относительные импорты относительно файловой системы, но не понимает соглашений сборщиков:

  • Разрешение node_modules отсутствует.
  • Карта exports package.json отсутствует.
  • Вывод расширения файла отсутствует (нужно указывать import './foo.mjs', а не import './foo').
  • Импорты HTTP отсутствуют.

Это соответствует спецификации ECMAScript без расширений, но не поведению Node. Хука для пользовательского разрешения модулей нет. Чтобы использовать пакеты npm, соберите их в один скрипт или модуль с помощью такого инструмента, как esbuild, прежде чем передавать его в Phasis.

Wasm

WebAssembly не реализован. Нет среды выполнения wasm для привязки PHP.

Пропускаемые API Web, доступные только в браузере

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 — нужны настоящие потоки ОС, которых PHP не предоставляет.
  • Service Workers, Push API, Notifications, BroadcastChannel — не нужны вне браузера.

Глобальные объекты Node

process, Buffer, __dirname, __filename, require() — их тоже нет. Это соглашения хост-среды Node, а не возможности языка.

При переносе кода Node замените require('foo') на import 'foo', замените process.env.X на привязанный к хосту env.X и передавайте пути, соответствующие __dirname, из PHP.

Точность десятичных чисел

Числа ECMAScript — это числа двойной точности IEEE 754. Десятичные вычисления с более чем 15 значащими цифрами теряют точность, как и в любом другом движке JS. Для точных вычислений с целыми числами используйте BigInt, а для точных вычислений с дробями — десятичную библиотеку JS.

Внутри PHP для BigInt используется bcmath; единственное реальное отличие от V8 заключается в том, что очень большие операции с bigint выполняются в PHP медленнее, чем в реализации на C++ в V8. Численная точность одинакова.