Asyncify et JSPI : changement de pile dans PHP WebAssembly
Asyncify permet au code C ou C++ synchrone d’interagir avec du JavaScript asynchrone. Techniquement, il enregistre toute la pile d’appels C avant de rendre la main au JavaScript, puis la restaure lorsque l’appel asynchrone est terminé. On parle de changement de pile.
La prise en charge du réseau dans la compilation WebAssembly de PHP est implémentée avec Asyncify. Lorsque PHP effectue une requête réseau, il rend la main au JavaScript, qui exécute la requête, puis reprend PHP lorsque la réponse est prête. Cela fonctionne suffisamment bien pour que la compilation PHP puisse appeler des API web, installer des dépendances Composer et même se connecter à un serveur MySQL.
Plantages Asyncify
Le changement de pile impose d’envelopper toutes les fonctions C susceptibles de figurer sur la pile d’appels au moment d’un appel asynchrone. Envelopper systématiquement chaque fonction C ajoute une surcharge importante, d’où le maintien d’une liste de noms de fonctions précis :
Malheureusement, l’absence d’un seul élément de cette liste provoque un plantage WebAssembly dès que cette fonction fait partie de la pile lors d’un appel asynchrone. Voici à quoi cela ressemble :

Asyncify peut lister automatiquement toutes les fonctions C requises lorsqu’il est compilé sans ASYNCIFY_ONLY, mais cette détection automatique est trop agressive et finit par lister environ 70 000 fonctions C, ce qui porte le temps de démarrage à 4,5 secondes. C’est pourquoi nous maintenons la liste à la main.
Pour en savoir plus, consultez l’issue GitHub 251.
Corriger les plantages Asyncify
La pull request 253 ajoute une commande fix-asyncify qui exécute une suite de tests dédiée et ajoute automatiquement à la liste ASYNCIFY_ONLY les fonctions C manquantes identifiées.
En cas de plantage comme ci-dessus, vous pouvez le corriger ainsi :
- Identifier un chemin de code PHP qui déclenche le plantage — la trace de pile dans le terminal devrait aider.
- Ajouter un cas de test qui reproduit le plantage dans
packages/php-wasm/node/src/test/php-asyncify.spec.ts - Exécuter :
npm run fix-asyncify - Commiter le cas de test, le fichier
Dockerfilemis à jour et le fichierPHP.wasmreconstruit
JSPI : l’alternative moderne à Asyncify
L’API JavaScript Promise Integration (JSPI) gère le changement de pile nativement dans V8, ce qui supprime le besoin d’envelopper les fonctions comme avec Asyncify. WordPress Playground propose désormais des builds JSPI en plus des builds Asyncify pour toutes les versions de PHP (7.4–8.5).
État actuel :
- Le CLI Playground détecte automatiquement le support JSPI et l’active — aucun drapeau manuel n’est nécessaire
- Node.js 23+ prend en charge JSPI nativement ; Node.js 22 exige le drapeau
--experimental-wasm-jspi(géré automatiquement par le CLI) - Node.js 24+ devrait proposer JSPI sans drapeau
- Le support navigateur varie : JSPI est disponible dans Chrome et les navigateurs basés sur Chromium derrière des drapeaux
Optimisation de la taille des binaires avec MAIN_MODULE=2
Les builds Asyncify et JSPI sont compilées avec le drapeau MAIN_MODULE=2 d’Emscripten, qui applique une élimination de code mort sur les symboles exportés. Seuls les symboles dont les extensions dynamiques ont réellement besoin sont exportés.
Impact :
- Taille totale des binaires réduite de 122 Mo (13,7 %)
- Fichiers
.wasmréduits de 109 Mo (16 %) - Code glue JavaScript réduit de 14,5 Mo (63 %)
Cette optimisation s’applique à toutes les versions de PHP (7.4–8.5) pour les cibles Node.js et Web. La liste des symboles exportés est centralisée dans le fichier Dockerfile, avec des exports conditionnels pour certaines extensions (par ex. __c_longjmp pour Xdebug, _wasm_recv pour Memcached).