Przejdź do treści

Znane ograniczenia

Phasis dąży do identycznego zachowania jak V8 we wszystkich obserwowalnych testach specyfikacji. Ta strona opisuje sytuacje, w których w praktyce ta zgodność się kończy — zwykle z powodu fundamentalnych różnic między interpreterem PHP przechodzącym po drzewie a silnikiem C++ kompilującym kod za pomocą JIT.

Limit wydajności

Silnik przechodzi po drzewie składniowym, korzysta z optymistycznej maszyny wirtualnej kodu bajtowego oraz transpilatora JsToPhp, który zamienia bloki intensywnie używające wartości liczbowych i zmiennych lokalnych (w tym kształty obj.method(), new Ctor() i this.member) na natywne domknięcia PHP, które kompilujący metodą śledzącego JIT kompilator PHP 8.5 może zamienić na kod maszynowy. Większość gorących ścieżek wykonania przebiega wewnątrz pętli switch maszyny wirtualnej lub bezpośrednio w kodzie PHP skompilowanym przez JIT — bez przechodzenia po AST — jednak każdy kod operacji maszyny wirtualnej nadal wiąże się z narzutem PHP, a kształt, którego JsToPhp jeszcze nie obsługuje, wraca do wolniejszej maszyny wirtualnej. Limit szybkości obciążeń zależnych od czasu obsługi operacji wynosi około 100× mniej niż V8.

W przypadku osadzonych obciążeń — uruchamiania logiki dostarczonej przez użytkownika na danych PHP, potoków przekształcania treści i reguł walidacji — limit rzadko ma znaczenie. V8 zajmuje 100 ms; Phasis zajmuje 10 s; oba czasy mieszczą się znacznie poniżej limitu czasu żądania. Zmniejszenie różnicy wymagałoby nauczenia JsToPhp obsługi większej liczby kształtów (wnioskowanie o typach parametrów, obniżanie await / yield*) albo bezpośredniego generowania kodów operacyjnych opcache zamiast przez eval(). Zob. Benchmarki § Plan rozwoju.

Współbieżność wielu agentów

Model JavaScript: SharedArrayBuffer + Atomics.wait + postMessage zakłada, że host może uruchamiać prawdziwe wątki systemu operacyjnego. PHP nie może tego robić bez pcntl_fork, które nie jest dostępne na każdym hoście.

Phasis symuluje pojedynczego agenta — Atomics.wait natychmiast zwraca „not-equal” lub „timed-out”, a notify zawsze zwraca 0. Dzięki temu przechodzą wszystkie testy test262 działające z jednym agentem (walidacja wejścia, ścieżki błędów, powiadomienia bez działania), co obejmuje 376 testów Atomics z 376.

Testy wymagające od dwóch współbieżnych agentów wzajemnej obserwacji nie są obsługiwane. Aby uzyskać prawdziwe przetwarzanie równoległe, użyj kolejki zadań na poziomie PHP (Redis, Beanstalkd, …).

Semantyka realmów

ShadowRealm jest zaimplementowane, ale każdy realm potomny działa w tym samym procesie PHP co host. Funkcje hosta mogą przenosić referencje między realmami, jeśli nie zadbasz o odizolowanie kodu PHP. Specyfikacja na to pozwala, ale V8 wymusza ściślejszą izolację dzięki oddzielnym instancjom v8::Isolate.

createRealm() (hak hosta test262) tworzy wewnętrznie nowy Phasis\Engine i udostępnia w nim bibliotekę standardową.

Wybór silnika wyrażeń regularnych

Phasis używa PCRE2 od PHP jako głównego zaplecza wyrażeń regularnych, a w przypadkach, których PCRE2 nie obsługuje dokładnie, stosuje własny mechanizm dopasowywania (lookbehind z przechwytywaniem, kwantyfikowane grupy z resetem, określona semantyka flagi /v). Własny mechanizm działa wolniej niż PCRE2 i jest używany wyłącznie wtedy, gdy mechanizm wyboru wykryje niezgodny wzorzec.

Istotną konsekwencją jest to, że tablica dopasowywania wielkości liter w PCRE2 może różnić się od wersji ICU w V8, zwłaszcza w starszych dystrybucjach Linux (Ubuntu 22.04 zawiera ICU 70; Phasis dołącza tablicę ICU 76+, aby uniknąć różnic między systemami hosta dla wzorców /iu).

Model pamięci

Garbage collector PHP zliczający referencje różni się od śledzącego kolektora V8. Obiekty PHP przechwycone przez domknięcia hosta pozostają aktywne, dopóki silnik przechowuje te domknięcia, a cykle między nimi nie są usuwane aż do zniszczenia Engine. W przypadku pamięci podręcznej, której nie chcesz utrzymywać w pamięci, użyj WeakMap / WeakRef.

process.memoryUsage() i podobne interfejsy introspekcji specyficzne dla V8 nie są zaimplementowane.

Moduł ładujący

Moduł ładujący ES rozwiązuje ścieżki importów względnych względem systemu plików, ale nie rozumie konwencji bundlerów:

  • Brak rozwiązywania node_modules.
  • Brak mapy package.json exports.
  • Brak wnioskowania o rozszerzeniu pliku (musisz zapisać import './foo.mjs', a nie import './foo').
  • Brak importów HTTP.

To zachowanie jest zgodne z podstawową specyfikacją ECMAScript, ale nie z zachowaniem Node. Nie ma haka pozwalającego użyć własnego mechanizmu rozwiązywania modułów. Aby użyć pakietów npm, spakuj je narzędziem takim jak esbuild do pojedynczego skryptu lub modułu przed przekazaniem go do Phasis.

Wasm

WebAssembly nie jest zaimplementowane. Nie ma środowiska uruchomieniowego wasm PHP, z którym można by je powiązać.

Pomijane internetowe API dostępne tylko w przeglądarkach

Phasis dostarcza pełny Web Platform Pack (URL, TextEncoder/TextDecoder, atob/btoa, structuredClone, performance, DOMException), pełny Fetch Pack (fetch, Request, Response, Headers, Body, AbortController/AbortSignal, Blob/File, FormData, EventTarget/Event, pełny WHATWG Streams, navigator), a także crypto (getRandomValues, randomUUID, pełny SubtleCrypto: digest, HMAC, AES-GCM/CBC/CTR, RSA-OAEP/PSS/PKCS1, ECDSA, ECDH, HKDF, PBKDF2), WebSocket (konstruktor + koder ramek RFC 6455 z wymiennym transportem), XMLHttpRequest (warstwa nad transportem fetch) oraz prawdziwą pętlę zdarzeń (setTimeout, setInterval, clearTimeout, clearInterval, queueMicrotask, AsyncContext). Zob. API internetowe.

Czego nie dostarczamy:

  • window, document, drzewo DOM — Phasis nie jest przeglądarką.
  • localStorage, sessionStorage, IndexedDB — kwestie po stronie hosta; za trwałość danych odpowiada osadzający.
  • Worker, SharedWorker, MessageChannel — wymagają prawdziwych wątków systemu operacyjnego, których PHP nie udostępnia.
  • Service Workers, Push API, Notifications, BroadcastChannel — poza przeglądarką nie mają zastosowania.

Globalne obiekty Node

process, Buffer, __dirname, __filename, require() — żaden z tych elementów również nie istnieje. To konwencje hosta Node, a nie funkcje języka.

Jeśli przenosisz kod Node, zastąp require('foo') elementem import 'foo', zamień process.env.X na env.X powiązany z hostem i przekaż ścieżki odpowiadające __dirname z poziomu PHP.

Precyzja dziesiętna

Liczby ECMAScript są liczbami zmiennoprzecinkowymi IEEE 754 double. Działania dziesiętne z więcej niż 15 cyframi znaczącymi tracą precyzję, tak jak w każdym innym silniku JS. Użyj BigInt do dokładnych obliczeń całkowitoliczbowych albo biblioteki dziesiętnej JS, jeśli potrzebujesz dokładnych obliczeń ułamkowych.

Wewnętrznie PHP używa bcmath do obsługi BigInt; jedyna rzeczywista różnica w porównaniu z V8 polega na tym, że bardzo duże operacje bigint działają wolniej w PHP niż w implementacji C++ V8. Poprawność numeryczna jest identyczna.