A complete and mature WebAssembly runtime for Java based on Wasmer.
Features:
- Easy to use: The
wasmer
API mimics the standard WebAssembly API, - Fast: The Wasmer JNI executes the WebAssembly modules as fast as possible, close to native speed,
- Safe: All calls to WebAssembly will be fast, but more importantly, completely safe and sandboxed.
Install
The Wasmer package is published in Bintray on the
wasmer/wasmer-jni
repository.
The JAR files are named as follows:
wasmer-jni-$(architecture)-$(os)-$(version).jar
. Thus, to include
Wasmer JNI as a dependency, write for instance:
dependencies {
implementation "org.wasmer:wasmer-jni-amd64-linux:0.3.0"
}
Note: It is also possible to download the Java JAR file from the Github releases page! If you need to produce a JAR for your own platform and architecture, see the Development Section to learn more.
Example
There is a toy program in java/src/test/resources/simple.rs
, written
in Rust (or any other language that compiles to WebAssembly):
#[no_mangle]
pub extern fn sum(x: i32, y: i32) -> i32 {
x + y
}
After compilation to WebAssembly, the
tests/resources/simple.wasm
binary file is generated. (Download
it).
Then, we can execute it in Java:
class Example {
public static void main(String[] args) {
// `simple.wasm` is located at `tests/resources/`.
Path wasmPath = Paths.get(new Example().getClass().getClassLoader().getResource("simple.wasm").getPath());
// Reads the WebAssembly module as bytes.
byte[] wasmBytes = Files.readAllBytes(wasmPath);
// Instantiates the WebAssembly module.
Instance instance = new Instance(wasmBytes);
// Calls an exported function, and returns an object array.
Object[] results = instance.exports.getFunction("sum").apply(5, 37);
System.out.println((Integer) results[0]); // 42
// Drops an instance object pointer which is stored in Rust.
instance.close();
}
}
There is more examples in the examples/
directory. Run them with the
Makefile
, such as: make run-example EXAMPLE=Simple
to run the
SimpleExample
example.
wasmer
library
API of the The root namespace is org.wasmer
.
Instance
class
The The Instance
constructor compiles and instantiates a WebAssembly
module. It is built upon bytes. From here, it is possible to call
exported functions, or exported memories. For example:
// Instantiates the WebAssembly module.
Instance instance = new Instance(wasmBytes);
// Calls an exported function.
Object[] results = instance.exports.getFunction("sum").apply(1, 2);
// Casts an object to an integer object because the result is an object array.
int result = (Integer) results[0];
System.out.println(result); // 3
// Drops an instance object pointer manually. Note that the garbage collector
// will call this method before an object is removed from the memory.
instance.close();
Exports
All exports, like functions or memories, are accessible on the
Instance.exports
field, which is of kind Exports
(a read-only
wrapper around a map of kind Map<String, exports.Export>
). The
Exports.get
method returns an object of type Export
. To
downcast it to an exported function or to an exported memory, you can
use the respective getFunction
or getMemory
methods. The following
sections describe each exports in details.
Exported functions
An exported function is a native Java closure (represented by the
exports.Function
class), where all arguments are automatically
casted to WebAssembly values if possible, and all results are of type
Object
, which can be typed to Integer
or Float
for instance.
Function sum = instance.exports.getFunction("sum");
Object[] results = sum.apply(1, 2);
System.out.println((Integer) results[0]); // 3
Exported memories
An exported memory is a regular Memory
class.
Memory memory = instance.exports.getMemory("memory_1");
See the Memory
class section for more information.
Module
class
The The Module.validate
static method checks whether a sequence of bytes
represents a valid WebAssembly module:
// Checks that given bytes represent a valid WebAssembly module.
boolean isValid = Module.validate(wasmBytes);
The Module
constructor compiles a sequence of bytes into a
WebAssembly module. From here, it is possible to instantiate it:
// Compiles the bytes into a WebAssembly module.
Module module = new Module(wasmBytes);
// Instantiates the WebAssembly module.
Instance instance = module.instantiate();
Serialization and deserialization
The Module.serialize
method and its complementary
Module.deserialize
static method help to respectively serialize and
deserialize a compiled WebAssembly module, thus saving the compilation
time for the next use:
// Compiles the bytes into a WebAssembly module.
Module module1 = new Module(wasmBytes);
// Serializes the module.
byte[] serializedModule = module1.serialize();
// Let's forget about the module for this example.
module1 = null;
// Deserializes the module.
Module module2 = Module.deserialize(serializedModule);
// Instantiates and uses it.
Object[] results = module2.instantiate().exports.getFunction("sum").apply(1, 2);
System.out.println((Integer) results[0]); // 3
Memory
class
The A WebAssembly instance has a linear memory, represented by the
Memory
class. Let's see how to read it. Consider the following Rust
program:
#[no_mangle]
pub extern fn return_hello() -> *const u8 {
b"Hello, World!\0".as_ptr()
}
The return_hello
function returns a pointer to a string. This string
is stored in the WebAssembly memory. Let's read it.
Instance instance = new Instance(wasmBytes);
// Gets the memory by specifying its exported name.
Memory memory = instance.exports.getMemory("memory");
// Gets the pointer value as an integer.
int pointer = (Integer) instance.exports.getFunction("return_hello").apply()[0];
// Reads the data from the memory.
ByteBuffer memoryBuffer = memory.buffer();
byte[] stringBytes = new byte[13];
memoryBuffer.position(pointer);
memoryBuffer.get(stringBytes);
System.out.println(new String(stringBytes)); // Hello, World!
instance.close();
Memory grow
The Memory.grow
methods allows to grow the memory by a number of pages (of 64KiB each).
// Grows the memory by the specified number of pages, and returns the number of old pages.
int oldPageSize = memory.grow(1);
Development
The Wasmer JNI library is based on the Wasmer runtime, which is written in Rust, and is compiled to a shared library. For your convenience, we produce one JAR (Java Archive) per architecture and platform. By now, the following are supported, consistently tested, and pre-packaged:
amd64-darwin
,amd64-linux
,amd64-windows
.
More architectures and more platforms will be added in a close future. If you need a specific one, feel free to ask!
If you want to build the extension you will need the following tools:
- Gradle, a package management tool,
- Rust, the Rust programming language,
- Java, because it's a Java project ;-).
$ git clone https://github.com/wasmerio/wasmer-java/
$ cd wasmer-java
To build the entire project, run the following command:
$ make build
To build the JAR package:
$ make package
This will generate the file build/libs/wasmer-jni-$(architecture)-$(os)-0.3.0.jar
.
Automatic dependencies per architecture and platform
It is possible to infer the archive appendix automatically, see how.
According the Gradle Jar
API,
the $(architecture)-$(os)
part is called the archive prefix. To
infer that appendix automatically to configure your dependencies, you
can use the following inferWasmerJarAppendix
function:
String inferWasmerJarAppendix() {
def nativePlatform = new org.gradle.nativeplatform.platform.internal.DefaultNativePlatform("current")
def arch = nativePlatform.architecture
def os = nativePlatform.operatingSystem
def arch_name
switch (arch.getName()) {
case ["x86_64", "x64", "x86-64"]:
arch_name = "amd64"
break;
default:
throw new RuntimeException("`wasmer-jni` has no pre-compiled archive for the architecture " + arch.getName())
}
def os_name
if (os.isMacOsX()) {
os_name = "darwin"
} else if (os.isLinux()) {
os_name = "linux"
} else if (os.isWindows()) {
os_name = "windows"
} else {
throw new RuntimeException("`wasmer-jni` has no pre-compiled archive for the platform " + os.getName())
}
return arch_name + "-" + os_name
}
Finally, you can configure your dependencies such as:
dependencies {
implementation "org.wasmer:wasmer-jni-" + inferWasmerJarAppendix() + ":0.3.0"
}
Testing
Run the following command:
$ make test
Note: Testing automatically builds the project.
Documentation
Run the following command:
$ make javadoc
Then open build/docs/javadoc/index.html
.
What is WebAssembly?
Quoting the WebAssembly site:
WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable target for compilation of high-level languages like C/C++/Rust, enabling deployment on the web for client and server applications.
About speed:
WebAssembly aims to execute at native speed by taking advantage of common hardware capabilities available on a wide range of platforms.
About safety:
WebAssembly describes a memory-safe, sandboxed execution environment […].
License
The entire project is under the MIT License. Please read the
LICENSE
file.