Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experiment: Build PHP with OPFS support #1030

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions opfs/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Originally forked from https://github.com/seanmorris/php-wasm
# ubuntu:lunar supports amd64 and arm64 (Apple Silicon) while
# emscripten/emsdk:3.1.24 supports amd64 only.
FROM ubuntu:lunar as emscripten

SHELL ["/bin/bash", "-c"]

ENV PKG_CONFIG_PATH /root/lib/lib/pkgconfig
ENV TIMER "(which pv > /dev/null && pv --name '${@}' || cat)"

WORKDIR /root
RUN mkdir lib

RUN set -euxo pipefail;\
apt-get update; \
apt-get --no-install-recommends -y install \
build-essential \
automake \
autoconf \
libxml2-dev \
libtool \
pkgconf \
flex \
make \
re2c \
gdb \
git \
pv \
ca-certificates \
curl \
wget \
unzip \
cmake \
python3

# Install Emscripten from the repository. We'd use the official
# Docker image, but there is no arm64 image available which makes
# the build take forever on Apple Silicon.
RUN ln -s /usr/bin/python3 /usr/bin/python
RUN git clone https://github.com/emscripten-core/emsdk.git && \
./emsdk/emsdk install 3.1.43 && \
/root/emsdk/emsdk activate 3.1.43
108 changes: 108 additions & 0 deletions opfs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Emscripten OPFS experiment

This is an attempt to build an Emscripten module that uses
OPFS as its filesystem.

To try it out locally, run:

```bash
python server.py
```

Then, go to http://localhost:8000/ and open the developer tools.

## OPFS crashes in Chrome

No matter what I did, I couldn't get OPFS to work in the browser.

It always crashes when creating a new file in the `/opfs` directory. This function call:

```c
void create_file() {
FILE *file = fopen("/opfs/test.txt", "w");
// ...
}
```

Invokes the following asynchronous JavaScript implementation:

```js
async function wasmfsOPFSGetOrCreateFile(parent, name, create) {
let parentHandle = wasmfsOPFSDirectoryHandles.get(parent);
let fileHandle;
try {
fileHandle = await parentHandle.getFileHandle(name, {create: create});
// ...
```

However, the `await` is deadly and crashes the WASM process:

```
support.cpp:39 Uncaught RuntimeError: unreachable
at wasmfs::handle_unreachable(char const*, char const*, unsigned int) (http://localhost:8000/opfs.wasm:wasm-function[327]:0xf3d3)
at (anonymous namespace)::OPFSDirectory::getChild(std::__2::basic_string<char, std::__2::char_traits<char>, std::__2::allocator<char>> const&) (http://localhost:8000/opfs.wasm:wasm-function[297]:0xba0e)
at wasmfs::Directory::Handle::getChild(std::__2::basic_string<char, std::__2::char_traits<char>, std::__2::allocator<char>> const&) (http://localhost:8000/opfs.wasm:wasm-function[406]:0x11c4c)
at wasmfs::path::(anonymous namespace)::getChild(std::__2::shared_ptr<wasmfs::Directory>, std::__2::basic_string_view<char, std::__2::char_traits<char>>, wasmfs::path::LinkBehavior, unsigned long&) (http://localhost:8000/opfs.wasm:wasm-function[439]:0x17a2f)
at wasmfs::path::(anonymous namespace)::doParseParent(std::__2::basic_string_view<char, std::__2::char_traits<char>>, std::__2::shared_ptr<wasmfs::Directory>, unsigned long&) (http://localhost:8000/opfs.wasm:wasm-function[438]:0x175f2)
at wasmfs::path::parseParent(std::__2::basic_string_view<char, std::__2::char_traits<char>>, unsigned int) (http://localhost:8000/opfs.wasm:wasm-function[436]:0x16ef2)
at __syscall_openat (http://localhost:8000/opfs.wasm:wasm-function[474]:0x1b10d)
at open (http://localhost:8000/opfs.wasm:wasm-function[53]:0x15a8)
at create_file (http://localhost:8000/opfs.wasm:wasm-function[47]:0x11e7)
at ret.<computed> (http://localhost:8000/opfs.js:2559:35)
```

It's not about the `getFileHandle` call. It's about asynchronous code. The same function with `await sleep(100);` crashes in the exact same way.

## Potential solutions

I tried building:

- With `-sASYNCIFY=1` and different ASYNCIFY settings and imports
- With `-sASYNCIFY=2` (after enabling JSPI at chrome://flags)
- With `-sWASM_WORKERS`

Unfortunately, the final bundle always crashes in the same way.

Weirdly, it crashes on file creation but not on directory creation. The `mkdir` code path seems to somehow support stack switching.

I don't have any futher ideas so I'll abandon these explorations for now and leave this PR for posterity.

## Implementation details

This PR explores Emscripten's new WASMFS filesystem with OPFS backend.

`main.c` contains the C code where the OPFS directory is created:

```c
int main() {
backend_t opfs = wasmfs_create_opfs_backend();
emscripten_console_log("created OPFS backend");
wasmfs_create_directory("/opfs", 0777, opfs);
}

void create_file() {
FILE *file = fopen("/opfs/test.txt", "w");
// ...
}
```

It's then built into a WASM module via `build.sh` (or `build-in-docker.sh` for convenience) and loaded in the browser by index.html.

## Building

Run `build-in-docker.sh`

## WASMFS Resources

There's no documentation out there, I've been learning from GitHub discussions and code in the Emscripten repo. Here's a few useful links:

- https://github.com/emscripten-core/emscripten/issues/15949
- https://github.com/emscripten-core/emscripten/issues/15041
- https://docs.google.com/document/d/1-ZxybGvz0nCqygUDuWxCcCBhCebev3EbUSYoSOlc49Q/edit#heading=h.xzptrog8pyxf
- https://emscripten.org/docs/api_reference/Filesystem-API.html#new-file-system-wasmfs
- https://github.com/emscripten-core/emscripten/blob/main/test/wasmfs/wasmfs_opfs.c
- https://github.com/orgs/emscripten-core/projects/1/views/1?filterQuery=
- https://github.com/emscripten-core/emscripten/pull/16813
- https://github.com/emscripten-core/emscripten/issues/18112
- https://github.com/emscripten-core/emscripten/blob/bb265ceb2e5340a9d5a08349522b94ea74aa9265/src/library_wasmfs_opfs.js
- https://github.com/emscripten-core/emscripten/issues/15976
6 changes: 6 additions & 0 deletions opfs/build-in-docker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash

docker build -t playground-php-wasm:base .
docker run --name test-opfs --rm -v "$(pwd)":/app -w /app \
playground-php-wasm:base bash /app/build.sh

15 changes: 15 additions & 0 deletions opfs/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash

source /root/emsdk/emsdk_env.sh
emcc main.c -o opfs.js \
-g3 \
-O0 \
-sFORCE_FILESYSTEM=1 \
-sEXPORTED_FUNCTIONS=["_main","_create_file"] \
-sEXPORTED_RUNTIME_METHODS=["FS","ccall"] \
-sWASMFS=1 \
-sASYNCIFY=1 \
-sASYNCIFY_IGNORE_INDIRECT=1 \
-sASYNCIFY_ADVISE=1 \
-sASYNCIFY_IMPORTS=["_main","_create_file","_open","__syscall_openat"]
# -sWASM_WORKERS # \
12 changes: 12 additions & 0 deletions opfs/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en-us">
<head>
<meta charset="utf-8" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<script type="text/javascript">
new Worker('index.js');
</script>
</body>
</html>
12 changes: 12 additions & 0 deletions opfs/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
var Module = {
preRun: [],
postRun: [
function () {
setTimeout(() => {
Module.ccall('create_file');
}, 1000);
},
],
};

importScripts('opfs.js');
33 changes: 33 additions & 0 deletions opfs/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#include <assert.h>
#include <dirent.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <emscripten/wasmfs.h>
#include <emscripten/console.h>

int main() {
printf("Hello, OPFS via WASMFS!\n");

backend_t opfs = wasmfs_create_opfs_backend();
emscripten_console_log("created OPFS backend");
int err = wasmfs_create_directory("/opfs", 0777, opfs);

return 0;
}

void create_file() {
FILE *file = fopen("/opfs/test.txt", "w");
if (file == NULL) {
printf("Error: Could not open file\n");
return;
}
fprintf(file, "Hello, OPFS via WASMFS!\n");
fclose(file);
}
Loading
Loading