القيود المعروفة
يهدف Phasis إلى مطابقة V8 تمامًا في كل اختبار مواصفات يمكن ملاحظته. تسرد هذه الصفحة المواضع التي تنتهي عندها هذه المطابقة عمليًا — وعادةً بسبب اختلافات جوهرية بين مفسّر PHP يتبع بنية الشجرة ومحرك C++ يستخدم التجميع الفوري JIT.
سقف الأداء
المحرك عبارة عن متتبع للشجرة مع آلة افتراضية للبايت كود تعمل بشكل انتهازي ومحوّل JsToPhp يخفض الأجسام كثيرة الأرقام والمتغيرات المحلية (بما في ذلك أشكال obj.method() وnew Ctor() وthis.member) إلى إغلاقات PHP أصلية يمكن لـJIT التتبعي في الإصدار 8.5 من PHP ترجمتها إلى شيفرة آلة. تجري معظم عمليات الإرسال الساخنة داخل حلقة التبديل في الآلة الافتراضية أو مباشرةً في PHP المترجمة بـJIT، لا عبر اجتياز شجرة البنية المجردة (AST)، لكن كل تعليمة تشغيل في الآلة الافتراضية تظل تتحمل كلفة إرسال PHP، وأي شكل لا يتعامل معه JsToPhp بعد يعود إلى الآلة الافتراضية الأبطأ. يبلغ الحد الأقصى للأحمال التي يحدها الإرسال نحو ~100× أبطأ من V8.
في أحمال التضمين — تشغيل منطق يقدمه المستخدم على بيانات PHP، ومسارات تحويل المحتوى، وقواعد التحقق — نادرًا ما يكون الحد الأقصى مهمًا. يستغرق V8 مدة 100 ms، بينما يستغرق Phasis مدة 10 s؛ وكلاهما أقل بكثير من مهلة الطلب. ويتطلب تقليص الفجوة أكثر تعليم JsToPhp مزيدًا من الأشكال (استنتاج أنواع المعاملات، وخفض await / yield*) أو إصدار تعليمات opcache مباشرةً بدلًا من المرور عبر eval(). راجع المعايير § خارطة الطريق.
التزامن متعدد الوكلاء
يفترض نموذج JavaScript المكوّن من SharedArrayBuffer + Atomics.wait + postMessage أن المضيف قادر على تشغيل خيوط نظام تشغيل فعلية. لا يستطيع PHP ذلك (من دون pcntl_fork، وهي غير متاحة على كل مضيف).
يحاكي Phasis وكيلًا واحدًا — إذ يعيد Atomics.wait فورًا «غير متساوٍ» أو «انتهت المهلة»، بينما يعيد notify القيمة 0 دائمًا. يجتاز هذا كل اختبارات test262 التي تعمل على وكيل واحد (التحقق من المدخلات، ومسارات الأخطاء، وإشعار بلا إجراء)، وتشمل 376 من أصل 376 اختبارًا لـAtomics.
الاختبارات التي تتطلب فعليًا وكيلين متزامنين يراقب أحدهما الآخر غير مدعومة. استخدم قائمة انتظار للعمال على مستوى PHP (Redis أو Beanstalkd أو …) لتحقيق التوازي الفعلي.
دلالات Realm
تم تنفيذ ShadowRealm، لكن كل Realm فرعي يعمل في عملية PHP نفسها التي يعمل فيها المضيف. قد تسرّب دوال المضيف مراجع بين الـRealms ما لم تحرص على عزل شيفرة PHP. تسمح المواصفة بذلك؛ أما V8 فيفرض عزلًا أشد باستخدام مثيلات v8::Isolate منفصلة.
ينشئ createRealm() (خطاف المضيف لـtest262) كائن Phasis\Engine جديدًا داخليًا ويربط المكتبة القياسية عبره.
اختيار محرك Regex
يستخدم 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() وواجهات برمجة التطبيقات الاستكشافية الأخرى الخاصة بـV8.
محمّل الوحدات
يحلّ محمّل وحدات ES الاستيرادات النسبية بالرجوع إلى نظام الملفات، لكنه لا يفهم اصطلاحات أدوات التجميع:
- لا يوجد حلّ لـ
node_modules. - لا توجد خريطة
package.jsonexports. - لا يوجد استنتاج لامتداد الملف (يجب كتابة
import './foo.mjs'، لاimport './foo'). - لا توجد استيرادات HTTP.
يتوافق هذا مع مواصفة ECMAScript الأساسية، لكنه لا يتوافق مع سلوك Node. لا توجد نقطة ربط لمحلّل وحدات مخصص. لاستخدام حزم npm، اجمعها باستخدام أداة مثل esbuild في سكربت أو وحدة واحدة قبل تمريرها إلى Phasis.
Wasm
لم يتم تنفيذ WebAssembly. لا يوجد وقت تشغيل wasm من PHP للربط به.
واجهات Web API الخاصة بالمتصفح التي نتجاوزها
يأتي Phasis مع حزمة منصة الويب كاملة (URL، TextEncoder/TextDecoder، atob/btoa، structuredClone، performance، DOMException)، وحزمة Fetch كاملة (fetch، Request، Response، Headers، Body، AbortController/AbortSignal، Blob/File، FormData، EventTarget/Event، وتدفقات WHATWG كاملة، و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 والإشعارات و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 إذا كنت تحتاج إلى حساب كسري دقيق.
يُستخدم bcmath الخاص بـPHP داخليًا من أجل BigInt؛ والفرق الفعلي الوحيد مقارنةً بـV8 هو أن عمليات bigint الكبيرة جدًا أبطأ في PHP منها في تنفيذ V8 بلغة C++. الدقة العددية متطابقة.