This library contains code for instantiating and running WASI modules. WASI is a virtual system interface that can give you some familiar posix-like syscalls and is supported by many of the compilers out there.
There are currently 2 versions of WASI at the moment, preview1 and preview2. This library is currently
aimed at preview1
.
Note: You might hear the terms
preview1/preview1
or0.1/0.2
when referring to the versions inside the WASI docs. And you might hearwasip1
when being used as a flag to a compiler target. We tend to prefer the nomenclature wasip1 / wasip2.
Although wasip1
is marked as "legacy", this is the version that nearly all compilers support when compiling to a Wasm target. For that reason we are aiming to have good support, but have no immediate plans to "complete" the implementation.
Please reach out if you'd like to see more done to support wasip1.
Here are some features we have basic coverage for currently:
- stdin / stdout / stderr
- environment variables
- command arguments
- clocks
- random
- basic reading and writing of files (through use of a virtual file system)
If your module calls a wasi function that we don't support, or uses a feature that we don't support, we will throw a WASMRuntimeException
.
For the most up-to-date info, and to see what specific functions we support, see the WasiPreview1.java class. We also have a table:
WASI Function | Supported | Notes |
---|---|---|
args_get | ✅ | |
args_sizes_get | ✅ | |
clock_res_get | 👷 | Clock IDs process_cputime_id and thread_cputime_id are not supported. realtime and monotonic are supported. |
clock_time_get | 👷 | Clock IDs process_cputime_id and thread_cputime_id are not supported. realtime and monotonic are supported. |
environ_get | ✅ | |
environ_sizes_get | ✅ | |
fd_advise | ❌ | |
fd_allocate | ❌ | |
fd_close | ✅ | |
fd_datasync | ❌ | |
fd_fdstat_get | ✅ | |
fd_fdstat_set_flags | ✅ | |
fd_fdstat_set_rights | 💀 | |
fd_filestat_get | ✅ | |
fd_filestat_set_size | ❌ | |
fd_filestat_set_times | ❌ | |
fd_pread | ❌ | |
fd_prestat_dir_name | ✅ | |
fd_prestat_get | ✅ | |
fd_pwrite | ❌ | |
fd_read | ✅ | |
fd_readdir | ✅ | |
fd_renumber | ❌ | |
fd_seek | ✅ | |
fd_sync | ❌ | |
fd_tell | ✅ | |
fd_write | ✅ | |
path_create_directory | ✅ | |
path_filestat_get | ✅ | |
path_filestat_set_times | ❌ | |
path_link | ❌ | |
path_open | ✅ | |
path_readlink | 👷 | |
path_remove_directory | ✅ | |
path_rename | ✅ | |
path_symlink | ❌ | |
path_unlink_file | ✅ | |
poll_oneoff | ❌ | |
proc_exit | ✅ | |
proc_raise | 💀 | |
random_get | ✅ | |
reset_adapter_state | ❌ | |
sched_yield | ✅ | |
set_allocation_state | ❌ | |
set_state_ptr | ❌ | |
sock_accept | ❌ | |
sock_recv | ❌ | |
sock_send | ❌ | |
sock_shutdown | ❌ |
Note: 💀 means the function is no longer part of WASI.
We do have intentions to support wasip2 in the future, however this work has not been started. Please reach out to us on zulip if you are interested in helping plan and execute this work.
As a host who is running Wasm modules, WASI is just a collection of host imports that you need to provide to a wasi-compiled module when instantiating it. You'll also need to configure some options for how these functions behave and what the module can and cannot do.
So to instantiate a WASI module you need an instance of WasiPreview1
. You can turn this instance into
import functions which can then be passed to the Module builder.
Download from the link or with curl:
curl https://raw.githubusercontent.com/dylibso/chicory/main/wasm-corpus/src/main/resources/compiled/hello-wasi.wat.wasm > hello-wasi.wasm
import com.dylibso.chicory.log.SystemLogger;
import com.dylibso.chicory.wasi.WasiOptions;
import com.dylibso.chicory.wasi.WasiPreview1;
import com.dylibso.chicory.wasm.Module;
import com.dylibso.chicory.wasm.Parser;
import com.dylibso.chicory.runtime.HostImports;
import com.dylibso.chicory.runtime.Instance;
import java.io.File;
var logger = new SystemLogger();
// let's just use the default options for now
var options = WasiOptions.builder().build();
// create our instance of wasip1
var wasi = new WasiPreview1(logger, WasiOptions.builder().build());
// turn those into host imports. Here we could add any other custom imports we have
var imports = new HostImports(wasi.toHostFunctions());
// create the module and connect imports
// this will execute the module if it's a WASI command-pattern module
Instance.builder(Parser.parse(new File("hello-wasi.wasm"))).withHostImports(imports).build();
Note: Take note that we don't explicitly execute the module. The module will run when you instantiate it. This is part of the WASI spec. They will implicitly call
_start
. To learn more read this blog post.
At the very least, you probably want to orchestrate stdin, stdout, and stderr of the module. Often, this is the way you communicate with basic WASI-enabled modules by way of the command pattern. In order to make it easy to manipulate these streams, we expose stdin as an InputStream and stdout/stderr as an OutputStream.
Download from the link or with curl:
curl https://raw.githubusercontent.com/dylibso/chicory/main/wasm-corpus/src/main/resources/compiled/greet-wasi.rs.wasm > greet-wasi.wasm
// Let's create a fake stdin stream with the bytes "Andrea"
var fakeStdin = new ByteArrayInputStream("Andrea".getBytes());
// We will create two output streams to capture stdout and stderr
var fakeStdout = new ByteArrayOutputStream();
var fakeStderr = new ByteArrayOutputStream();
// now pass those to our wasi options builder
var wasiOpts = WasiOptions.builder().withStdout(fakeStdout).withStderr(fakeStderr).withStdin(fakeStdin).build();
var wasi = new WasiPreview1(logger, wasiOpts);
var imports = new HostImports(wasi.toHostFunctions());
// greet-wasi is a rust program that greets the string passed in stdin
// instantiating will execute the module if it's a WASI command-pattern module
Instance.builder(Parser.parse(new File("greet-wasi.wasm"))).withHostImports(imports).build();
// check that we output the greeting
assert(fakeStdout.toString().equals("Hello, Andrea!"));
// there should be no bytes in stderr!
assert(fakeStderr.toString().equals(""));