WASI Library
WebAssembly System Interface (WASI) provides a modular and secure system interface designed to enable Wasm module to interact with the underlying operating system in a platform-independent manner.
MicroEJ provides its own WASI Library which implements a subset of WASI 0.1 APIs specification.
Moreover, some APIs have been extended to support certain POSIX functions that are not available in wasi-libc such as socket, bind, connect, etc.
Usage
To use the WASI Add-on Library, add the following to the project build file:
implementation("ej.library.runtime:wasi:0.2.0")
<dependency org="ej.library.runtime" name="wasi" rev="0.2.0"/>
In your application code, call one of the WASI context initialization methods before calling any Managed C function:
Wasi.init(WasmModule)— initializes WASI context for the given Wasm module instance and pre-opens the root directory/.Wasi.init(WasmModule,String[])— initializes WASI context for the given Wasm module instance and pre-opens only the specified directories array. Passnullto pre-open none.
If a directory is not pre-opened, it cannot be accessed by the Wasm module.
Here is an example:
C source code (
my_app.c):#include <stdlib.h> #include <stdio.h> #include <time.h> void print_time() { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); printf("%lld.%.9ld\n", (long long)ts.tv_sec, ts.tv_nsec); }
Java source code (
MyApp.java):package com.mycompany; import ej.lang.Alias; import ej.lang.WasmModule; import ej.wasi.Wasi; import java.io.IOException; @Alias("my_app") public final class MyApp extends WasmModule { public static void main(String[] args) throws IOException { MyApp myApp = new MyApp(); Wasi.init(myApp); // Call the "print_time" Wasm function myApp.printTime(); } @Alias("print_time") public native synchronized void printTime(); }
In this example, the implementation of the POSIX-compliant C functions clock_gettime(...) and printf(...) relies on the WASI functions clock_time_get(...) and fd_write(...) which are provided by the WASI Add-on Library.
Note
WASI cannot be used by more than one Wasm module within a single Application. Otherwise, calls to WASI methods may result in undefined behavior. In particular, in Multi-Sandbox mode, WASI APIs must not be exposed as Kernel APIs. This restriction allows each Application and the Kernel to use WASI independently, while still limiting usage to a single Wasm module per context.
WASI APIs
List of the currently suppported WASI APIs:
Group |
WASI APIs |
FileSystem
|
path_open, fd_readdir, fd_close, fd_read, fd_pread, fd_write,fd_pwrite, fd_seek, fd_fdstat_get, fd_fdstat_set_flags, fd_prestat_get,fd_prestat_dir_name, fd_tell, fd_filestat_get, path_unlink_file,path_remove_directory, path_filestat_get |
Sockets |
|
Clocks |
|
Process |
|
High-Level WASI-Based POSIX APIs
List of the currently supported high-level WASI-based POSIX APIs.
The pthread and semaphore entries are not exhaustive, and a * stands for the whole function family:
Group |
High-Level WASI-Based POSIX APIs |
FileSystem
|
open, fopen, fdopendir, close, fclose, read, write, fread,fwrite, lseek, stat, fstat, readdir, fstatat, fileno, fflush,access |
Sockets |
|
Clocks |
|
Process |
|
StdIO |
|
Pthreads
|
Threads:
pthread_create, pthread_join, pthread_detach, pthread_self.Attributes:
pthread_attr_*. Synchronization: pthread_mutex_*, pthread_cond_*,pthread_rwlock_*, pthread_barrier_*, pthread_spin_*.Thread-local storage:
pthread_key_*, pthread_getspecific,pthread_setspecific, pthread_once. |
Semaphores
|
sem_init, sem_destroy, sem_wait, sem_timedwait, sem_trywait,sem_post, sem_getvalue |
High-Level Extended WASI POSIX APIs
List of the high-level extended WASI-based POSIX APIs that provide functionalities not available in the wasi-libc:
Group |
High-Level Extended WASI POSIX APIs |
Sockets |
|
Miscs |
|
WASI Threads
MicroEJ provides support of POSIX threads (pthreads) in Managed C through the implementations of Threads Proposal for WebAssembly and WASI threads specification.
This allows running the following code:
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>
void* thread_entry_point(void *ctx) {
// Called in a new pthread
return 0;
}
int main() {
pthread_attr_t new_thread_attr;
pthread_t new_thread;
// Initialize the stack size
pthread_attr_init(&new_thread_attr);
int res = pthread_attr_setstacksize(&new_thread_attr, 2048);
if(res != 0){
// Unable to set the stack size
return -1;
}
pthread_create(&new_thread, &new_thread_attr, &thread_entry_point, NULL);
// ...
pthread_join(new_thread, NULL);
return 0;
}
Note
By default, if not specified, the WASI libc stack size is 128KB.
It is strongly recommended to set the stack size at thread creation using the pthread_attr_setstacksize function.
When WASI threads are enabled, each new pthread created by Managed C
code (via pthread_create) is backed by a new module instance that shares the linear memory of
the parent instance. Each pthread instance has its own stack and TLS allocated within the shared
linear memory, allowing threads to run independently while accessing the same data.
Enable WASI Threads
Compile and link your Wasm Module with these additional Clang options:
--target=wasm32-wasi-threads -pthread.Make your
WasmModulesubclass implement theWasiThreadModuleinterface and declare thewasi_thread_startmethod as an instancenative synchronizedmethod. Then callWasi.init(WasmModule)orWasi.init(WasmModule,String[])before calling any Managed C function:package com.mycompany; import ej.lang.Alias; import ej.lang.WasmModule; import ej.wasi.Wasi; import ej.wasi.thread.WasiThreadModule; import java.io.IOException; @Alias("my_app") public final class MyApp extends WasmModule implements WasiThreadModule { public static void main(String[] args) { MyApp myApp = new MyApp(); try { Wasi.init(myApp); } catch (IOException e) { // error during initialization } // ... } @Override public native synchronized void wasi_thread_start(int threadId, int args); }
Note
When the Wasm Module is not compiled with WASI Threads enabled, then WasmModule.clone() will throw a CloneNotSupportedException.
