Skip to content

Commit

Permalink
[PHP] Use JSPI when available, fall back to Asyncify (#1867)
Browse files Browse the repository at this point in the history
 ## Motivation

Ships every PHP.wasm build and dependency in two versions: JSPI, Asyncify. Updates `@php-wasm/web` and `@php-wasm/node` to use the JSPI version when the current runtime supports it, and and fall back to Asyncify otherwise.

Why use JSPI? See #134. Tl;dr it will make PHP.wasm a whole lot more reliable.

 ## Implementation details

This builds on top of the explorations done in #1339 – check the description and discussion there for the full "getting there" journey and detailed learnings.

 ### @php-wasm/compile

The main Makefile ships an `_asyncify` and a `_jspi` version of every build task. Libraries, such as `libcurl` and `libedit`, store now each ship an Asyncify build and a JSPI build. Every JSPI build uses `-sSUPPORT_LONGJMP=wasm -fwasm-exceptions` flags. Asyncify builds are the same as before this PR and don't use those flags.

 ### @php-wasm/web and @php-wasm/node

* PHP builds are shipped in `jspi` and `asyncify` subdirectories.
* Emscripten doesn't export `free()` when using JSPI so we're exporting our own `wasm_free()` function.
* `getPHPLoaderModule()` uses [wasm-feature-detect](https://github.com/GoogleChromeLabs/wasm-feature-detect)  to check for JSPI support and load the right build.
* Asynchronous JavaScript functions were moved from `phpwasm-emscripten-library.js` to `php_wasm.c` using the `EM_ASYNC_JS` macro for JSPI builds and `EM_JS` macro for Asyncify builds.
* Unit tests are now ran separately on JSPI and Asyncify builds.

 ## Runtime support as of Oct 10th, 2024

JSPI is supported in:

- ✅ Google Chrome with `#enable-experimental-webassembly-jspi` enabled at `chrome://flags`, or with sites where the JSPI origin trial is enabled. playground.wordpress.net is enrolled in the origin trial.
- ✅ Node.js v22+ with `--experimental-wasm-stack-switching` feature flag.
- ✅ Deno, with `--v8-flags=--experimental-wasm-jspi` feature flag
- ✅ [Firefox](https://bugzilla.mozilla.org/show_bug.cgi?id=1850627)
- ? Chrome-based browsers like Edge
- ❌ Safari
- ❌ Non-Chrome, non-Firefox web browsers
- ❌ Node.js <= 21
- ❌ Non-v8 JS runtimes like Bun

 ## Testing instructions

The E2E tests are a great source of insights:

* Chrome supports JSPI
* Firefox supports JSPI but has a separate implementation
* Safari doesn't support JSPI and will use the Asyncify build

We should also run the unit tests in Node v20 and v23 using with the experimental JSPI support flag.
  • Loading branch information
adamziel committed Oct 9, 2024
1 parent e969da7 commit 3bebb24
Show file tree
Hide file tree
Showing 660 changed files with 155,676 additions and 3,728 deletions.
9 changes: 8 additions & 1 deletion .github/actions/prepare-playground/action.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
name: Clone the repository, install dependencies, and configures nx variables

inputs:
node-version:
type: string
required: true
description: 'Node.js version to use'
default: '18'

runs:
using: 'composite'
steps:
Expand All @@ -8,7 +15,7 @@ runs:
run: git fetch origin trunk --depth=1 --recurse-submodules
- uses: actions/setup-node@v4
with:
node-version: 18
node-version: ${{ inputs.node-version }}
- name: Cache node modules
id: cache-nodemodules
uses: actions/cache@v2
Expand Down
18 changes: 16 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- uses: ./.github/actions/prepare-playground
- run: npx nx affected --target=lint
- run: npx nx affected --target=typecheck
test-unit:
test-unit-asyncify:
runs-on: ubuntu-latest
needs: [lint-and-typecheck]
steps:
Expand All @@ -32,6 +32,20 @@ jobs:
submodules: true
- uses: ./.github/actions/prepare-playground
- run: node --expose-gc node_modules/nx/bin/nx affected --target=test --configuration=ci
# JSPI doesn't seem to be supported in Node.js 22
# It used to work, see https://github.com/WordPress/wordpress-playground/pull/1339.
# These days, though, running our JSPI builds throws the following error: TypeError: WebAssembly.Suspending is not a constructor
# test-unit-jspi:
# runs-on: ubuntu-latest
# needs: [lint-and-typecheck]
# steps:
# - uses: actions/checkout@v4
# with:
# submodules: true
# - uses: ./.github/actions/prepare-playground
# with:
# node-version: 22
# - run: node --experimental-wasm-jspi --experimental-wasm-stack-switching --expose-gc node_modules/nx/bin/nx affected --target=test --configuration=ci
test-e2e:
runs-on: ubuntu-latest
needs: [lint-and-typecheck]
Expand Down Expand Up @@ -123,7 +137,7 @@ jobs:
deploy_docs:
if: github.ref == 'refs/heads/trunk' && github.event_name == 'push'
# Add a dependency to the build job
needs: [test-unit, test-e2e, build]
needs: [test-unit-asyncify, test-unit-jspi, test-e2e, build]
name: 'Deploy doc site'

# Grant GITHUB_TOKEN the permissions required to make a Pages deployment
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,10 @@ npx nx start php-wasm-cli
# Build latest WordPress releases
npx nx bundle-wordpress:all playground-wordpress-builds

# Recompile PHP 5.6 - 8.2 releases to .wasm for web
# Recompile PHP 7.0 - 8.3 releases to .wasm for web
npx nx recompile-php:all php-wasm-web

# Recompile PHP 5.6 - 8.2 releases to .wasm for node
# Recompile PHP 7.0 - 8.3 releases to .wasm for node
npx nx recompile-php:all php-wasm-node

# Builds the documentation site
Expand Down
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

66 changes: 45 additions & 21 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,54 @@
"prepublishOnly": "npm run build",
"mindmap": "cd packages/meta/src/mindmap/v2 && php -S 127.0.0.1:5269",
"preview": "nx preview playground-website",
"recompile:php:web": "nx recompile-php:all php-wasm-web",
"recompile:php:web:7.0": "nx recompile-php php-wasm-web --PHP_VERSION=7.0",
"recompile:php:web:7.1": "nx recompile-php php-wasm-web --PHP_VERSION=7.1",
"recompile:php:web:7.2": "nx recompile-php php-wasm-web --PHP_VERSION=7.2",
"recompile:php:web:7.3": "nx recompile-php php-wasm-web --PHP_VERSION=7.3",
"recompile:php:web:7.4": "nx recompile-php php-wasm-web --PHP_VERSION=7.4",
"recompile:php:web:8.0": "nx recompile-php php-wasm-web --PHP_VERSION=8.0",
"recompile:php:web:8.1": "nx recompile-php php-wasm-web --PHP_VERSION=8.1",
"recompile:php:web:8.2": "nx recompile-php php-wasm-web --PHP_VERSION=8.2",
"recompile:php:web:8.3": "nx recompile-php php-wasm-web --PHP_VERSION=8.3",
"recompile:php:node": "nx recompile-php:all php-wasm-node",
"recompile:php:node:7.0": "nx recompile-php php-wasm-node --PHP_VERSION=7.0",
"recompile:php:node:7.1": "nx recompile-php php-wasm-node --PHP_VERSION=7.1",
"recompile:php:node:7.2": "nx recompile-php php-wasm-node --PHP_VERSION=7.2",
"recompile:php:node:7.3": "nx recompile-php php-wasm-node --PHP_VERSION=7.3",
"recompile:php:node:7.4": "nx recompile-php php-wasm-node --PHP_VERSION=7.4",
"recompile:php:node:8.0": "nx recompile-php php-wasm-node --PHP_VERSION=8.0",
"recompile:php:node:8.1": "nx recompile-php php-wasm-node --PHP_VERSION=8.1",
"recompile:php:node:8.2": "nx recompile-php php-wasm-node --PHP_VERSION=8.2",
"recompile:php:node:8.3": "nx recompile-php php-wasm-node --PHP_VERSION=8.3",
"release": "lerna publish patch --yes --no-private --loglevel=verbose",
"test": "node --expose-gc node_modules/nx/bin/nx run-many --all --target=test",
"fix-asyncify": "node packages/php-wasm/node/bin/rebuild-while-asyncify-functions-missing.mjs",
"typecheck": "nx run-many --all --target=typecheck",
"prepare": "husky install"
"prepare": "husky install",
"recompile:php": "npm run recompile:php:web && npm run recompile:php:node",
"recompile:php:web": "nx recompile-php:all php-wasm-web",
"recompile:php:web:jspi:all": "nx recompile-php:jspi:all php-wasm-web",
"recompile:php:web:jspi:8.3": "nx recompile-php:jspi php-wasm-web -- --PHP_VERSION=8.3",
"recompile:php:web:jspi:8.2": "nx recompile-php:jspi php-wasm-web -- --PHP_VERSION=8.2",
"recompile:php:web:jspi:8.1": "nx recompile-php:jspi php-wasm-web -- --PHP_VERSION=8.1",
"recompile:php:web:jspi:8.0": "nx recompile-php:jspi php-wasm-web -- --PHP_VERSION=8.0",
"recompile:php:web:jspi:7.4": "nx recompile-php:jspi php-wasm-web -- --PHP_VERSION=7.4",
"recompile:php:web:jspi:7.3": "nx recompile-php:jspi php-wasm-web -- --PHP_VERSION=7.3",
"recompile:php:web:jspi:7.2": "nx recompile-php:jspi php-wasm-web -- --PHP_VERSION=7.2",
"recompile:php:web:jspi:7.1": "nx recompile-php:jspi php-wasm-web -- --PHP_VERSION=7.1",
"recompile:php:web:jspi:7.0": "nx recompile-php:jspi php-wasm-web -- --PHP_VERSION=7.0",
"recompile:php:web:asyncify:all": "nx recompile-php:asyncify:all php-wasm-web",
"recompile:php:web:asyncify:8.3": "nx recompile-php:asyncify php-wasm-web -- --PHP_VERSION=8.3",
"recompile:php:web:asyncify:8.2": "nx recompile-php:asyncify php-wasm-web -- --PHP_VERSION=8.2",
"recompile:php:web:asyncify:8.1": "nx recompile-php:asyncify php-wasm-web -- --PHP_VERSION=8.1",
"recompile:php:web:asyncify:8.0": "nx recompile-php:asyncify php-wasm-web -- --PHP_VERSION=8.0",
"recompile:php:web:asyncify:7.4": "nx recompile-php:asyncify php-wasm-web -- --PHP_VERSION=7.4",
"recompile:php:web:asyncify:7.3": "nx recompile-php:asyncify php-wasm-web -- --PHP_VERSION=7.3",
"recompile:php:web:asyncify:7.2": "nx recompile-php:asyncify php-wasm-web -- --PHP_VERSION=7.2",
"recompile:php:web:asyncify:7.1": "nx recompile-php:asyncify php-wasm-web -- --PHP_VERSION=7.1",
"recompile:php:web:asyncify:7.0": "nx recompile-php:asyncify php-wasm-web -- --PHP_VERSION=7.0",
"recompile:php:node": "nx recompile-php:all php-wasm-node",
"recompile:php:node:jspi:all": "nx recompile-php:jspi:all php-wasm-node",
"recompile:php:node:jspi:8.3": "nx recompile-php:jspi php-wasm-node -- --PHP_VERSION=8.3",
"recompile:php:node:jspi:8.2": "nx recompile-php:jspi php-wasm-node -- --PHP_VERSION=8.2",
"recompile:php:node:jspi:8.1": "nx recompile-php:jspi php-wasm-node -- --PHP_VERSION=8.1",
"recompile:php:node:jspi:8.0": "nx recompile-php:jspi php-wasm-node -- --PHP_VERSION=8.0",
"recompile:php:node:jspi:7.4": "nx recompile-php:jspi php-wasm-node -- --PHP_VERSION=7.4",
"recompile:php:node:jspi:7.3": "nx recompile-php:jspi php-wasm-node -- --PHP_VERSION=7.3",
"recompile:php:node:jspi:7.2": "nx recompile-php:jspi php-wasm-node -- --PHP_VERSION=7.2",
"recompile:php:node:jspi:7.1": "nx recompile-php:jspi php-wasm-node -- --PHP_VERSION=7.1",
"recompile:php:node:jspi:7.0": "nx recompile-php:jspi php-wasm-node -- --PHP_VERSION=7.0",
"recompile:php:node:asyncify:all": "nx recompile-php:asyncify:all php-wasm-node",
"recompile:php:node:asyncify:8.3": "nx recompile-php:asyncify php-wasm-node -- --PHP_VERSION=8.3",
"recompile:php:node:asyncify:8.2": "nx recompile-php:asyncify php-wasm-node -- --PHP_VERSION=8.2",
"recompile:php:node:asyncify:8.1": "nx recompile-php:asyncify php-wasm-node -- --PHP_VERSION=8.1",
"recompile:php:node:asyncify:8.0": "nx recompile-php:asyncify php-wasm-node -- --PHP_VERSION=8.0",
"recompile:php:node:asyncify:7.4": "nx recompile-php:asyncify php-wasm-node -- --PHP_VERSION=7.4",
"recompile:php:node:asyncify:7.3": "nx recompile-php:asyncify php-wasm-node -- --PHP_VERSION=7.3",
"recompile:php:node:asyncify:7.2": "nx recompile-php:asyncify php-wasm-node -- --PHP_VERSION=7.2",
"recompile:php:node:asyncify:7.1": "nx recompile-php:asyncify php-wasm-node -- --PHP_VERSION=7.1",
"recompile:php:node:asyncify:7.0": "nx recompile-php:asyncify php-wasm-node -- --PHP_VERSION=7.0"
},
"private": true,
"dependencies": {
Expand Down Expand Up @@ -73,6 +96,7 @@
"sha1": "1.1.1",
"unzipper": "0.10.11",
"vite-plugin-api": "1.0.4",
"wasm-feature-detect": "1.8.0",
"wouter": "3.3.5",
"xterm": "5.3.0",
"xterm-addon-fit": "0.8.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ To find out more about each step, refer directly to the [Dockerfile](https://git

### Building

To build all PHP versions, run `nx recompile-php:all php-wasm-web` (or `php-wasm-node`) in the repository root. You'll find the output files in `packages/php-wasm/php-web/public`. To build a specific version, run `nx recompile-php:all php-wasm-node --PHP_VERSION=8.0`.
To build all PHP versions, run `nx recompile-php:all php-wasm-web` (or `php-wasm-node`) in the repository root. You'll find the output files in `packages/php-wasm/php-web/public`. To build a specific version, run `nx recompile-php:all php-wasm-node --PHP_VERSION=8.0 --WITH_JSPI=yes` (and repeat with `--WITH_JSPI=no`).

### PHP extensions

Expand Down
Loading

0 comments on commit 3bebb24

Please sign in to comment.