Zum Inhalt springen

Bekannte Einschränkungen

Phasis soll sich bei allen beobachtbaren Spezifikationstests wie V8 verhalten. Diese Seite beschreibt, wo diese Übereinstimmung in der Praxis endet — meist aufgrund grundlegender Unterschiede zwischen einem Tree-Walking-PHP-Interpreter und einer JIT-kompilierten C++-Engine.

Leistungsgrenze

Die Engine ist ein Tree-Walker mit einer opportunistischen Bytecode-VM und einem JsToPhp-Transpiler, der numerische und lokale Variablen stark nutzende Funktionskörper (einschließlich der Formen obj.method(), new Ctor() und this.member) in native PHP-Closures umwandelt, die der Tracing-JIT von PHP 8.5 zu Maschinencode kompilieren kann. Der Großteil der häufigen Dispatches findet in der Switch-Schleife der VM oder direkt in JIT-kompiliertem PHP statt – nicht durch das Durchlaufen des AST. Jeder VM-Opcode verursacht jedoch weiterhin Dispatch-Overhead von PHP, und Formen, die JsToPhp noch nicht unterstützt, fallen auf die langsamere VM zurück. Bei workloads, die durch Dispatch begrenzt sind, liegt die Obergrenze bei ungefähr ~100× langsamer als V8.

Bei eingebetteten Anwendungen – etwa beim Ausführen von nutzerdefinierter Logik auf PHP-Daten, in Inhaltsverarbeitungspipelines oder Validierungsregeln – ist diese Obergrenze selten entscheidend. V8 benötigt 100 ms, Phasis 10 s; beides liegt deutlich unter dem Zeitlimit für Anfragen. Um den Abstand weiter zu verkleinern, müsste JsToPhp mehr Formen unterstützen (Inferenz von Parametertypen, Absenkung von await / yield*) oder direkt Opcache-Opcodes statt über eval() ausgeben. Siehe Benchmarks § Roadmap.

Nebenläufigkeit mehrerer Agenten

Das Modell von JavaScript mit SharedArrayBuffer + Atomics.wait + postMessage setzt voraus, dass der Host echte OS-Threads ausführen kann. PHP kann das nicht (ohne pcntl_fork, das nicht auf jedem Host verfügbar ist).

Phasis simuliert einen einzelnen Agenten – Atomics.wait gibt sofort „not-equal“ oder „timed-out“ zurück, notify gibt immer 0 zurück. Damit bestehen alle test262-Tests, die mit einem einzelnen Agenten laufen (Eingabevalidierung, Fehlerpfade, wirkungslose notify-Aufrufe); das umfasst 376 von 376 Atomics-Tests.

Tests, die tatsächlich zwei nebenläufige Agenten erfordern, die einander beobachten, werden nicht unterstützt. Für echte Parallelität kannst du eine Worker-Warteschlange auf Ebene von PHP verwenden (Redis, Beanstalkd, …).

Realm-Semantik

ShadowRealm ist implementiert, aber jedes untergeordnete Realm läuft im selben PHP-Prozess wie der Host. Host-Funktionen können Referenzen zwischen Realms weitergeben, wenn du nicht darauf achtest, den Code von PHP abzuschirmen. Die Spezifikation erlaubt das; V8 erzwingt eine strengere Isolation durch separate v8::Isolate-Instanzen.

createRealm() (test262-Host-Hook) erstellt intern ein neues Phasis\Engine und bindet die Standardbibliothek ein.

Wahl der Regex-Engine

Phasis verwendet PCRE2 von PHP als primäres Regex-Backend und einen eigenen Matcher für Fälle, die PCRE2 nicht exakt verarbeiten kann (Lookbehind mit Captures, quantifizierte Gruppen mit Reset, bestimmte Semantiken von /v-Flags). Der eigene Matcher ist langsamer als PCRE2 und kommt nur zum Einsatz, wenn der Dispatch ein inkompatibles Muster erkennt.

Eine bemerkenswerte Folge: PCRE2 kann eine andere Tabelle für Groß- und Kleinschreibung verwenden als die ICU-Version von V8, besonders bei älteren Linux-Distributionen (Ubuntu 22.04 liefert ICU 70 mit; Phasis bündelt eine ICU-76+-Tabelle zur Fallfaltung, um Abweichungen des Hosts bei /iu-Mustern auszugleichen).

Speichermodell

Die GC von PHP mit Referenzzählung unterscheidet sich von V8s Tracing-Collector. Von Host-Closures erfasste PHP-Objekte bleiben so lange erhalten, wie die Engine diese Closures hält; Zyklen zwischen ihnen werden erst aufgeräumt, wenn Engine zerstört wird. Verwende WeakMap / WeakRef für Caches, die nicht dauerhaft erhalten bleiben sollen.

process.memoryUsage() und ähnliche V8-spezifische Introspektions-APIs sind nicht implementiert.

Modullader

Der ES-Modullader löst relative Imports anhand des Dateisystems auf, versteht aber keine Konventionen von Bundlern:

  • Keine node_modules-Auflösung.
  • Keine package.json-exports-Map.
  • Keine Ergänzung von Dateiendungen (du musst import './foo.mjs' statt import './foo' schreiben).
  • Keine HTTP-Imports.

Das entspricht der reinen ECMAScript-Spezifikation, nicht aber dem Verhalten von Node. Es gibt keinen Hook für einen benutzerdefinierten Modul-Resolver. Um npm-Pakete zu verwenden, bündle sie mit einem Tool wie esbuild zu einem einzelnen Skript oder Modul, bevor du es an Phasis übergibst.

Wasm

WebAssembly ist nicht implementiert. Es gibt keine PHP-Wasm-Laufzeit, an die eine Bindung möglich wäre.

Browser-APIs, die wir auslassen

Phasis enthält das vollständige Web Platform Pack (URL, TextEncoder/TextDecoder, atob/btoa, structuredClone, performance, DOMException), das vollständige Fetch Pack (fetch, Request, Response, Headers, Body, AbortController/AbortSignal, Blob/File, FormData, EventTarget/Event, vollständige WHATWG Streams, navigator), außerdem crypto (getRandomValues, randomUUID, vollständiges SubtleCrypto: Digest, HMAC, AES-GCM/CBC/CTR, RSA-OAEP/PSS/PKCS1, ECDSA, ECDH, HKDF, PBKDF2), WebSocket (Konstruktor + RFC-6455-Frame-Codec, austauschbarer Transport), XMLHttpRequest (auf dem Fetch-Transport aufbauend) und eine echte Event-Loop (setTimeout, setInterval, clearTimeout, clearInterval, queueMicrotask, AsyncContext). Siehe Web APIs.

Was nicht enthalten ist:

  • window, document, der DOM-Baum – Phasis ist kein Browser.
  • localStorage, sessionStorage, IndexedDB – Aufgaben des Hosts; der Embedder ist für die Persistenz zuständig.
  • Worker, SharedWorker, MessageChannel – erfordern echte OS-Threads, die PHP nicht bietet.
  • Service Workers, Push API, Benachrichtigungen, BroadcastChannel – außerhalb eines Browsers irrelevant.

Node-Globals

process, Buffer, __dirname, __filename, require() – keines davon ist vorhanden. Das sind Konventionen der Node-Hostumgebung, keine Sprachfunktionen.

Wenn du Node-Code portierst, ersetze require('foo') durch import 'foo', tausche process.env.X gegen ein an den Host gebundenes env.X aus und übergib gleichwertige Pfade für __dirname aus PHP.

Dezimalgenauigkeit

ECMAScript-Zahlen sind IEEE-754-Doubles. Bei Dezimalrechnungen mit mehr als 15 signifikanten Stellen geht Genauigkeit verloren, genau wie bei jeder anderen JS-Engine. Verwende BigInt für exakte Ganzzahlrechnung oder eine JS-Dezimalbibliothek, wenn du exakte Bruchrechnungen brauchst.

PHPs bcmath wird intern für BigInt verwendet; der einzige echte Unterschied zu V8 ist, dass sehr große BigInt-Operationen in PHP langsamer sind als in V8s C++-Implementierung. Die numerische Korrektheit ist identisch.