Asyncify y JSPI: cambio de pila en PHP WebAssembly
Asyncify permite que el código C o C++ síncrono interactúe con JavaScript asíncrono. Técnicamente, guarda toda la pila de llamadas de C antes de ceder el control a JavaScript y luego la restaura cuando termina la llamada asíncrona. Esto se denomina cambio de pila.
La compatibilidad de red en la compilación WebAssembly de PHP se implementa con Asyncify. Cuando PHP realiza una solicitud de red, cede el control a JavaScript, que ejecuta la solicitud y reanuda PHP cuando la respuesta está lista. Funciona lo bastante bien como para que la compilación de PHP pueda solicitar APIs web, instalar paquetes de Composer e incluso conectarse a un servidor MySQL.
Fallos de Asyncify
El cambio de pila exige envolver todas las funciones C que puedan encontrarse en la pila de llamadas en el momento de una llamada asíncrona. Envolver por completo cada función C añade una sobrecarga considerable, por eso mantenemos una lista de nombres de funciones concretas:
Por desgracia, omitir un solo elemento de esa lista provoca un fallo de WebAssembly siempre que esa función forme parte de la pila de llamadas cuando se hace una llamada asíncrona. Se ve así:

Asyncify puede listar automáticamente todas las funciones C necesarias cuando se compila sin ASYNCIFY_ONLY, pero esa detección automática es demasiado agresiva y acaba listando unas 70.000 funciones C, lo que aumenta el tiempo de arranque a 4,5 s. Por eso mantenemos la lista a mano.
Si quieres más detalles, consulta la issue 251 en GitHub.
Corregir fallos de Asyncify
El pull request 253 añade un comando fix-asyncify que ejecuta una batería de pruebas especializada y añade automáticamente a la lista ASYNCIFY_ONLY las funciones C faltantes identificadas.
Si te encuentras con un fallo como el anterior, puedes solucionarlo así:
- Identificar una ruta de código PHP que provoque el fallo: el rastreo de pila en la terminal debería ayudar.
- Añadir un caso de prueba que reproduzca el fallo en
packages/php-wasm/node/src/test/php-asyncify.spec.ts - Ejecutar:
npm run fix-asyncify - Hacer commit del caso de prueba, del Dockerfile actualizado y del PHP.wasm reconstruido
JSPI: la alternativa moderna a Asyncify
La API JavaScript Promise Integration (JSPI) gestiona el cambio de pila de forma nativa en V8, eliminando la necesidad de envolver funciones como en Asyncify. WordPress Playground ahora incluye compilaciones JSPI junto con las de Asyncify para todas las versiones de PHP (7.4–8.5).
Estado actual:
- El CLI de Playground detecta automáticamente el soporte JSPI y lo activa: no hace falta usar flags manualmente
- Node.js 23+ admite JSPI de forma nativa; Node.js 22 requiere el flag
--experimental-wasm-jspi(el CLI lo gestiona automáticamente) - Se espera que Node.js 24+ tenga JSPI sin flag
- El soporte en navegadores varía: JSPI está disponible en Chrome y navegadores basados en Chromium detrás de flags
Optimización del tamaño del binario con MAIN_MODULE=2
Las compilaciones Asyncify y JSPI se construyen con el flag MAIN_MODULE=2 de Emscripten, que elimina código muerto en los símbolos exportados. Solo se exportan los símbolos que las extensiones dinámicas necesitan de verdad.
Impacto:
- Tamaño total de binarios reducido en 122 MB (13,7 %)
- Archivos
.wasmreducidos en 109 MB (16 %) - Código glue en JavaScript reducido en 14,5 MB (63 %)
Esta optimización aplica a todas las versiones de PHP (7.4–8.5) tanto para Node.js como para Web. La lista de símbolos exportados se gestiona de forma centralizada en el Dockerfile, con exportaciones condicionales para extensiones concretas (p. ej., __c_longjmp para Xdebug, _wasm_recv para Memcached).