Porting PHP to WebAssembly using WASI
At VMware OCTO WasmLabs one of our main goals is to grow the WebAssembly ecosystem by helping developers adopt this new and exciting technology. One of our first projects was a WordPress instance that ran entirely in the browser. This was accomplished by using Emscripten to compile a PHP runtime into WebAssembly that could run in the browser. This was possible because Emscripten leverages the JavaScript engine to either provide or emulate certain functionality that the PHP runtime expects. However, this dependency makes it difficult to run it in server environments where there is no access to a JavaScript engine... until now that's it!
We are really glad to announce that we have a first build of PHP for
the wasm32-wasi
target! It will work with any runtime supporting the
WebAssembly System Interface (WASI), which is
most of them. ✨
This first cut is definitely a bit rough and missing some functionality, but is enough to run WordPress on the server side using mod_wasm. We did so in the Open Source spirit of release early, release often and Reid Hoffman's philosophy of "If you're not embarrassed by the first version of your product, you've launched too late".
The rest of this article explores why we are doing this and how you can get it working in your environment.
Background
WebAssembly (and WASI in particular) is gaining traction in scripting environments. We have two recent examples of popular language interpreters being ported to Wasm, CPython and Ruby. This is excellent news for all of us excited about WebAssembly! 🎉
PHP is another popular server side
language. We thought that it would be very interesting to have PHP
compiled to the wasm32-wasi
target, so that we allow the vast number
of projects written in this programming language to run on
WebAssembly. In other words, in order to run PHP in a Wasm runtime, we
will compile the mainstream PHP interpreter, which is written in C, to
WebAssembly and use it in turn to run the code for the PHP
applications.
A tale of two builds
As mentioned earlier, WasmLabs has done past work to run WordPress in
the browser. As part of this
work, we built the PHP interpreter to WebAssembly using the almighty
emscripten
toolchain. You can find all the details in the blog post.
emscripten
is very powerful and tackles a lot of complexity for
developers. For example, there are a number of syscalls implemented in
the JS glue code, which in turn relies on a JavaScript engine.
However, in order to build PHP without the need of a JavaScript engine
a different kind of build is necessary. Enter wasi-sdk
.
wasi-sdk
wasi-sdk is a WASI-enabled
C/C++ toolchain. The code is compiled and linked by Clang from the
LLVM project, while the language runtime comes from the wasi-libc
project. This allows us to do builds for the wasm32-wasi
target.
wasi-sdk
's primary goal is to build C/C++ programs targeting the
server side, this is, run in a WebAssembly runtime such as
Wasmtime,
WasmEdge... instead of a browser — usually
equipped with a fully-fledged JavaScript engine. —
Now that we have the toolchain that we'll use for the job, we need to decide what version of PHP we want to aim for.
PHP versions
We initially chose PHP 7.3.33, because we wanted the sqlite amalgamation to still be present in the PHP tree, which was removed in PHP 7.4.
This is the reason why the patch that we are publishing today is produced against PHP 7.3.33. We are currently working on patches for the latest release of PHP 7 as well as PHP 8.
Run PHP (wasm32-wasi) in Apache with mod_wasm
We have recently released the mod_wasm
Apache httpd
module. With
mod_wasm, you can run
WebAssembly modules within the Apache httpd server.
With the work presented here we can leverage mod_wasm
to run
php-cgi
as a WebAssembly module within Apache's httpd.
The patch
The patch we are releasing today is just an early version — it truly requires a significant amount of yak-shaving -. Our intent is to share it with the broader PHP and WebAssembly communities so that we can get feedback and evolve it to contribute upstream.
Challenges
Despite wasi-sdk
making the porting process relatively easy given
the early days of the overall ecosystem, there are issues that we had
to tackle. We describe some of those below.
setjmp/longjmp
These functions are commonly used for non-local jumps in programs
(control flow that deviates from the usual subroutine call and return
sequence). What this means in practice is that we have a goto
feature with the possibility to save the current stack, and come back
to this state somewhat later.
As an example, this is used to implement asynchronous features in synchronous programs, or alternative control flows, such as exception handling, fibers...
Due to WebAssembly's synchronous nature, it is not trivial to
implement such a feature. emscripten
has support for this feature
baked in. However, for a solution based in wasi-sdk
, a new approach
is required, such as the Asyncify
strategy.
Luckily, the binaryen
project in their wasm-opt
tool has functionality to asyncify a WebAssembly
module,
so that we could implement the missing bits in the PHP interpreter to
take advantage of it.
We have not implemented this yet, so that we have stripped away the calls to setjmp/longjmp for now.
Processes
WebAssembly does not have the concept of operating system
processes. Many of the syscalls that have to do with processes had to
either be hardcoded (such as getpid
), or be converted to
no-operations.
Filesystem
Given the nature of WASI's capability-oriented filesystem, there are subtle differences when it comes to accessing files. Particularly, because it's not WASI's aim to fulfill the everything-is-a-file Unix philosophy.
There is work in progress as well in this front.
Most of the filesystem syscalls that are not commonly used have been converted to no-operations for now.
Networking
WASI does not have full support for sockets yet. It allows us to accept incoming connections, receive and send messages, but at the moment it's not possible to open a socket; it has to be provided by the host. There is work in progress to improve their support, but it's still early to work on this for the PHP port.
As a result, we have stripped all networking capabilities from PHP for now.
WASIX
Although we have not included it in our preliminary work, we believe
that WASIX by SingleStore
Labs might be worth adding as an extra dependency when building
projects in which wasi-libc
is not providing enough functionality.
We are still exploring this possibility, When it comes to contributing
to upstream projects there are trade-offs to measure such as including
similar changes for wasm32-wasi
support in multiple projects versus
adding an extra dependency such as WASIX.
In short, we are open to feedback from both communities!
Running a PHP script
We want to make it easier for users to test our work. We have created a container image that you can use to run a simple PHP script, or even your own custom PHP programs!
docker run -p8080:8080 projects.registry.vmware.com/wasmlabs/containers/php-mod-wasm:hello-world@sha256:716518f5a264b63b3f00ce717b83af9a2c407df49b482dc36ce0d2882c731449
And now, we can perform a request to it:
❯ curl -vvv http://localhost:8080/
* Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.83.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Fri, 21 Oct 2022 06:13:03 GMT
< Server: Apache/2.4.54 (Unix)
< X-Powered-By: PHP/7.3.33
< Content-Length: 13
< Content-Type: text/html; charset=UTF-8
<
* Connection #0 to host localhost left intact
Hello, world
Running your own PHP programs in Apache
In order to run your own PHP application you only have to mount the PHP project directory in the expected location:
docker run -p8080:8080 -v /path/to/php/project:/usr/local/apache2/htdocs projects.registry.vmware.com/wasmlabs/containers/php-mod-wasm:hello-world@sha256:716518f5a264b63b3f00ce717b83af9a2c407df49b482dc36ce0d2882c731449
Now you can visit your PHP project at http://localhost:8080
!
As an example, you can find how to run WordPress on Wasm with Apache
Feel free to experiment with your own applications, we are eager to learn about your findings. Your feedback will help us prioritize the remaining work.
Wrapping up
This work is not finished yet, in fact we are just getting started! We would love your feedback and contributions to continue to improve language support for WebAssembly and move forward the current state of the art of WebAssembly tooling.