This project aims to provide a generic framework for WASM programs built using WASI and run on the Web platform using JavaScript. It is meant to be portable across projects and avoids encoding project-specific logic in its runtime.
While the framework builds off of itself, it is not all-or-nothing. You‘re free to use some components if parts of the overall runtime model don’t align with your particular use case.
Table of Contents
wassh is the project for building OpenSSH for WASM & WASI. As part of the development, we found that the WASI project lacked any real JS binding support. There is generated emscripten code, but that was built once a while ago, and hasn’t been updated since. Which means wassh will need to develop its own JS runtime.
As we fleshed things out, we recognized that a lot of the framework is not specific to wassh and could be reused by other WASM applications that want to run on the web. The market here is probably not significant (compared to e.g. emscripten), but it’s also probably not nothing, and we already need to do the majority of the work regardless. Most notably, the fact that WASM/WASI require all syscalls be handled synchronously while the JS world (and many of its APIs) can only be satisfied asynchronously.
Writing JS support code is basically writing an OS. The WASM code uses the WASI C library which makes syscalls to the JS world, and the JS world manages all the standard OS state (open files, etc...) while servicing the syscall requests.
As WASI is still under development, we often require current browser runtimes. No attempt is made to provide backwards compatibility or transpiling.
Note that the supported WASI API version matches the aforementioned WASI SDK version only.
These framework APIs are generally the ones we expect people to use. There are some more utility/internal APIs available if desired; see the API Reference for more details.
The SyscallHandler API is really how you bind your JS world to the WASM world. It is responsible for actually handling the syscalls via whatever unique state or paradigms used in your JS application.
For simple programs, it is expected that people will use Process.Foreground and SyscallEntry.WasiPreview1 and SyscallHandler.DirectWasiPreview1 APIs unmodified, perhaps with their own additional SyscallHandler class for things DirectWasiPreview1 does not support.
For complicated programs, it is expected that people will use Process.Background SyscallEntry.WasiPreview1 and SyscallHandler.ProxyWasiPreview1 and SyscallHandler.DirectWasiPreview1 APIs unmodified, and provide their own Worker and SyscallHandler implementations to round things out.
While the WASI API includes some basic networking support (sock_recv
, sock_send
, and sock_shutdown
), they haven't really been fleshed out upstream as of yet, so they are currently stubs here too.
See the wassh docs for details on how it implements networking support.
Check out [html/example.html].
At a very high level:
WASM -> SyscallEntry -> SyscallHandler -> -> SyscallHandler -> SyscallEntry -> WASM
SyscallEntry classes provide the initial entry points from the WASM world by implementing the WASI API. The function arguments are tightly coupled to that WASM world: constants reflect the WASI C library, pointers are absolute offsets into the program's memory to structures (which are also defined by the WASI C library). Their job is to unpack & translate the arguments to more natural JavaScript APIs before handing off execution to the SyscallHandler APIs.
They use a consistent naming convention like sys_<WASI syscall name>
, so the fd_write
WASI API syscall will be implemented here as sys_fd_write
.
Once the WASI arguments have been unpacked, handle_<WASI syscall name>
will be called in the SyscallHandler implementation. The APIs will often be simpler than the WASI API, so consult the API Reference section below.
In cases of errors, the errno value will always be returned directly. If cases of success, the handler will either return ESUCCESS (for simpler syscalls, or when outputs are via arguments), or return an object for more complicated outputs in which case ESUCCESS is always assumed.
SyscallHandler classes provide the actual implementation of syscalls. They use a consistent naming convention like handle_<WASI syscall name>
, so the fd_write
WASI API syscall will be implemented as handle_fd_write
, although the function signature will be different. Consult the API Reference section below for the exact arguments.
There are two major variants: direct & proxied. Direct handlers must be synchronous as the WASM runtime does not support asynchronous calls. Proxied handlers may be asynchronous as they run in a different thread -- they may be marked async and/or return Promises that resolve to the right value (NB: in case of errors, return appropriate errno values instead of rejecting the promise).
Replace with generated docs?
See the common libapps HACK.md for details.