Exploring WebAssembly and testing in Java through TeaVM.
This is just to explore a bit about WebAssembly and specially in Java.
- W3C standard
- Presented in June 2015, released in March 2017, W3C recommendation since December 2019
- Binary instruction format (bytecode)
- Executable in a virtual machine (wasm runtime)
- Portable (not architecture or microprocessor dependent), x86 / ARM / RISC-V compatible
- Secure (runs in a sandbox, separate from the host runtime)
- Compilable from several languages (currently 40+ languages), C, C++, Rust, Go, and more recently Java, Kotlin, C#, Dart, Swift, Zig, ... and even interpreted languages such as PHP, Python or Ruby via interpreter compilation
- Executable in a browser (compatible with Firefox, Chrome, Safari, Opera, Edge since 2017) via Web APIs
- Executable on a server or in a container via WASI / WasmEdge
- Lightweight (small binaries), and performance close to native (> JavaScript)
- Create binaries for the web from any language, executable anywhere
- Create libraries that can be used across multiple languages
- Create web applications that can use several languages on the same page
Quote from Solomon Hykes (co-founder of Docker) :
If WASM+WASI existed in 2008, we wouldn't have needed to create Docker. That's how important it is. WebAssembly on the server is the future of computing
WASM and Docker can be integrated via Docker WASM (beta) and/or container2wasm.
Linux/Windows containers depend on the processor architecture and the operating system (on Linux, you can only run Linux containers, and on Windows only Windows containers (or Linux, but via a VM through Hyper-V)), this is not the case with WASM containers.
Programs must be compiled to obtain WebAssembly modules. For interpreted languages, the interpreter is compiled into WebAssembly.
-
For C / C++ (every language that uses LLVM) :
emscripten
sudo apt install emscripten emcc hello.c -o hello.html
-
For Rust :
wasm-pack
cargo install wasm-pack wasm-pack build --target web
-
For Flutter (Dart) : via
js_interop
package and flag--wasm
(on browsers that supportWasmGC
)flutter build web --wasm
-
For Go : Native since Go 1.11 via environment variables
GOOS=js GOARCH=wasm go build -o main.wasm
-
For Java : JVM bytecode to WebAssembly via Bytecoder, TeaVM or Cheerpj
java -jar bytecoder-cli.jar -classpath=. -mainclass=Hello -builddirectory=. -backend=wasm
-
Currently, you need to activate the feature in Docker Desktop
-
Then specify the runtime and platform at runtime :
docker run --runtime=io.containerd.wasmedge.v1 --platform=wasi/wasm mywasm
Image to WASM via container2wasm :
Converting image to WASM, i.e. :
c2w ubuntu:22.04 /tmp/wasm/out.wasm
Running in a browser through Apache, i.e. :
c2w ubuntu:22.04 /tmp/out-js2/htdocs/out.wasm
cp -R ./examples/wasi-browser/* /tmp/out-js2/ && chmod 755 /tmp/out-js2/htdocs
docker run --rm -p 8080:80 \
-v "/tmp/out-js2/htdocs:/usr/local/apache2/htdocs/:ro" \
-v "/tmp/out-js2/xterm-pty.conf:/usr/local/apache2/conf/extra/xterm-pty.conf:ro" \
--entrypoint=/bin/sh httpd -c 'echo "Include conf/extra/xterm-pty.conf" >> /usr/local/apache2/conf/httpd.conf && httpd-foreground'
Statistics 2023
Also used in various blockchains (Cosmos, Polkadot, MultiversX, Near Protocol) !
Some WASM applications that run in the browser :
-
C++ :
- Google Earth : https://earth.google.com/web
- Kunky Karts (jeu) : https://www.funkykarts.rocks/demo.html
- Quake & Quake 2 : https://quake.m-h.org.uk/ & https://quake2.m-h.org.uk/
-
PHP :
- Wordpress : https://wordpress.wasmlabs.dev or https://playground.wordpress.net/
- Drupal & more : https://seanmorris.github.io/php-wasm
-
Kotlin :
- JetSnack : https://zal.im/wasm/jetsnack/
-
Go :
- Chess (compilé via TinyGo) : https://marianogappa.github.io/cheesse-examples/)
-
Rust :
- Python interpreter : https://rustpython.github.io/demo/
-
Linux :
- Linux simple https://bellard.org/jslinux/vm.html?url=alpine-x86.cfg&mem=192
- Linux simple with networking and C : https://ja.nsommer.dk/articles/linux-and-tiny-c-compiler-in-the-browser-part-one.html
- Linux with Python sur RISC V : https://ktock.github.io/container2wasm-demo/riscv64-python.html
- Linux with networking through Tailscale (and Python) : https://webvm.io/
- Linux via CheerpX (with Python, Ruby, NodeJs, C++) : https://repl.leaningtech.com/?python3=
-
Other tools :
- JupyterLite : https://jupyterlite.github.io/demo
List of projects made with WebAssembly : https://madewithwebassembly.com/
- Despite the promise of speed, the tools I've tried are pretty slow
- Most features are experimental, almost exclusively in preview or alpha versions
- The standard is incomplete and continues to evolve (rapidly though)
- Difficult to debug and profile (lack of tools)
Potential uses :
- Training, easily give users a ready-to-use environment (i.e. WordPress, Linux, Python, etc.)
- Testing new architectures, new distributions, etc.
- Libraries (shared across different systems / languages), even if TeaVM is not designed for this currently
TeamVM is a Java bytecode compiler that emits JavaScript and/or WebAssembly to be executed in the browser.
- It does not require source code, only compiled files (not like GWT, for example)
- it also supports Kotlin and Scala
Here is a minimal example to compile a Java function into WebAssembly and then use it in JavaScript within an HTML file.
-
Create a new Maven project. Here's a basic pom.xml configuration that includes the
teavm-maven-plugin
to compile Java to WebAssembly :<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>teavm-wasm-example</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.teavm</groupId> <artifactId>teavm-classlib</artifactId> <version>${teavm.version}</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.teavm</groupId> <artifactId>teavm-maven-plugin</artifactId> <version>0.9.2</version> <executions> <execution> <goals> <goal>compile</goal> </goals> <configuration> <mainClass>com.example.HelloWasm</mainClass> <targetDirectory>${project.basedir}/src/main/webapp/wasm</targetDirectory> <targetFileName>helloworld</targetFileName> <targetType>WEBASSEMBLY</targetType> <optimizationLevel>FULL</optimizationLevel> </configuration> </execution> </executions> </plugin> </plugins> </build> </project>
-
Create a minimal Java class with a simple static method that you want to compile to WebAssembly and export it :
package com.example; import org.teavm.interop.Export; public class HelloWasm { public static void main(String[] args) {} @Export(name = "add") public static int add(int a, int b) { return a + b; } }
As stated in the docs, TeaVM is not designed to port libraries, so we should always have a "main" method, to serve as an entry point.
-
Create the HTML and JavaScript to Use WASM :
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>TeaVM WASM Example</title> <script src="wasm/helloworld.wasm-runtime.js"></script> </head> <body> <h1>TeaVM WASM Example</h1> <p id="output"></p> <script> async function loadWasm() { const teavmInstance = await TeaVM.wasm.load('wasm/sumwasm.wasm'); const result = teavmInstance.instance.exports.add(10, 20); document.getElementById('output').textContent = 'Result : ' + result; } loadWasm(); </script> </body> </html>
-
Build the project :
mvn clean package
After building, the WebAssembly file helloworld.wasm should be located in src/main/webapp/wasm/.
It should also have generated a helloworld.wasm-runtime.js file, designed to handle the instantiation and management of the WebAssembly module. The script sets up necessary functions and imports, making it easier to work with the WebAssembly module (it is loaded in our HTML file).
-
Run the HTML file :
Make sure to copy the HTML file in the proper location (above the wasm directory containing the .wasm and .js files) then just run it with your preferred browser, it should display
Result : 30
.
This project contains 3 examples :
HelloJs
: compiled to Javascript, to demonstrate how to generate a JavaScript file from Java, that can be used from HTMLSumWasm
: compiled to WASM, to demonstrate how to export a Java function to WebAssembly module and call it from JavascriptHelloWasm
: compiled to WASM, to demonstrate how to export a Java function to WebAssembly module and call it from Javascript, and also how to import a javascript function that can be redefined in JavaScript (2-way interaction)
Note
The HelloWasm
example does not work as expected (will display "undefined" instead of the hello world string),
this is because WebAssembly itself does not natively support string types (or any other complex data structures),
so managing strings between Java, WebAssembly and JavaScript involves some additional steps.
I think it's a bit of a tinkering, so I haven't gone any further for the moment, I prefer to wait for improvements to the framework so that this can be managed more easily.
Configuration is done via the Maven plugin teavm-maven-plugin
, see TeaVM.
The plugin defines an execution for each of the example, you can at the configuration in the pom.xml.
Basically, for WASM examples, TeaVM compiles the Java class into a WASM binary, the JavaScript loads this WASM file, instantiate it, and call the exported methods. The result is displayed on the webpage.
TeaVM automatically creates corresponding JS files containing the necessary JavaScript functions that the WebAssembly module requires, it also contains helpers to handle the instantiation and management of the WebAssembly module, this is why we load the corresponding JavaScript runtime file in each HTML file.
In case you don't want to generate / include these files, you will need to define the imports manually, for example :
// define the imports required by TeaVM, including the logInt, logString, and currentTimeMillis functions
const imports = {
teavm: {
// provide the current time in milliseconds
currentTimeMillis: () => Date.now(),
// log strings passed from WASM to the browser's console
logString: (address) => {
const memory = instance.exports.memory;
const bytes = new Uint8Array(memory.buffer, address);
let str = '';
for (let i = 0; i < bytes.length && bytes[i] !== 0; i++) {
str += String.fromCharCode(bytes[i]);
}
console.log(str);
},
// log integers passed from WASM to the browser's console
logInt: (value) => {
console.log(value);
}
...
}
};
// instantiate the WebAssembly module with the imports
const result = await WebAssembly.instantiate(bytes, imports);
Simply package the project to generate the WASM and JS binaries :
mvn clean package
The TeaVM Maven plugin will generate the files in the target/webapp directory.
Then copy the HTML files into the target/webapp/ folder and launch them in your preferred browser.
General Public License (GPL) v3
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.