Wasm Labs

Porting PHP to WebAssembly using WASI

By Rafael Fernández
At 2022 / 10 15 mins reading

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.

PHP running with Apache's mod_wasm showing a phpinfo call rendered in a browser

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.

Do you want to stay up to date with WebAssembly and our projects?