diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..164f159
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,3 @@
+# Shell scripts must keep LF endings, also in Windows checkouts with
+# core.autocrlf=true, so they run under WSL and Linux.
+*.sh text eol=lf
diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index 0e4f73f..e99000b 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -1,10 +1,11 @@
-# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
-# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven
-
-# This workflow uses actions that are not certified by GitHub.
-# They are provided by a third-party and are governed by
-# separate terms of service, privacy policy, and support
-# documentation.
+# Builds the Maven reactor (shuttle-java, the example and the WIT tooling)
+# and runs its tests.
+#
+# - linux: native libraries for Linux x86_64 and aarch64 via `cross`; all modules.
+# - windows: native library built on the host (MSVC); shuttle-java and the example
+# only, because shuttle-wit-parser has no Windows native library yet.
+#
+# setup-rust-toolchain sets RUSTFLAGS=-D warnings, so Rust warnings fail the build.
name: Java CI with Maven
@@ -15,11 +16,13 @@ on:
branches: [main]
jobs:
- build:
+ linux:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions-rust-lang/setup-rust-toolchain@v1
+ with:
+ target: wasm32-unknown-unknown # for the valuetest fixture
- uses: actions/checkout@v5
- name: Set up JDK 21
uses: actions/setup-java@v5
@@ -27,9 +30,24 @@ jobs:
java-version: "21"
distribution: temurin
cache: maven
- - name: Install additional wasm targets for rust
- run: rustup target add wasm32-wasip1 wasm32-wasip2 && rustup target add wasm32-wasip1-threads --toolchain nightly
- name: Install cargo cross
run: cargo install cross
- name: Build with Maven
- run: mvn -P build-linux-x86-64,build-linux-aarch64,build-windows-x86_64 -Dcargo.profile=release -Dcargo.profile.folder=release -B package --file pom.xml
+ run: mvn -P build-linux-x86-64,build-linux-aarch64 -Dcargo.profile=release -Dcargo.profile.folder=release -B package --file pom.xml
+
+ windows:
+ runs-on: windows-latest
+ timeout-minutes: 30
+ steps:
+ - uses: actions-rust-lang/setup-rust-toolchain@v1
+ with:
+ target: wasm32-unknown-unknown # for the valuetest fixture
+ - uses: actions/checkout@v5
+ - name: Set up JDK 21
+ uses: actions/setup-java@v5
+ with:
+ java-version: "21"
+ distribution: temurin
+ cache: maven
+ - name: Build with Maven
+ run: mvn -P build-host-windows-x86_64 -pl shuttle-java,shuttle-java-example -am -Dcargo.profile=release -Dcargo.profile.folder=release -B package --file pom.xml
diff --git a/.github/workflows/native.yml b/.github/workflows/native.yml
new file mode 100644
index 0000000..e4be225
--- /dev/null
+++ b/.github/workflows/native.yml
@@ -0,0 +1,59 @@
+# Builds the Shuttle native workspace (native/) on all six target platforms
+# and runs the capture/restore verification on each.
+name: Native (Shuttle)
+
+on:
+ push:
+ branches: [main]
+ paths: ["native/**", ".github/workflows/native.yml"]
+ pull_request:
+ paths: ["native/**", ".github/workflows/native.yml"]
+ workflow_dispatch:
+
+jobs:
+ # The C guests are plain wasm32 and identical everywhere: build them once.
+ guests:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v5
+ - name: Install clang and lld
+ run: sudo apt-get update && sudo apt-get install -y clang lld
+ - name: Build guests
+ run: sh native/guests/build.sh
+ - uses: actions/upload-artifact@v4
+ with:
+ name: guests
+ path: native/guests/out/*.wasm
+
+ build:
+ needs: guests
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - { name: linux-x86_64, os: ubuntu-latest }
+ - { name: linux-aarch64, os: ubuntu-24.04-arm }
+ - { name: windows-x86_64, os: windows-latest }
+ - { name: windows-aarch64, os: windows-11-arm }
+ - { name: macos-x86_64, os: macos-15-intel }
+ - { name: macos-aarch64, os: macos-latest }
+ name: build (${{ matrix.name }})
+ runs-on: ${{ matrix.os }}
+ defaults:
+ run:
+ shell: bash
+ working-directory: native
+ steps:
+ - uses: actions/checkout@v5
+ - uses: actions-rust-lang/setup-rust-toolchain@v1
+ with:
+ cache-workspaces: native
+ rustflags: ""
+ - uses: actions/download-artifact@v4
+ with:
+ name: guests
+ path: native/guests/out
+ - name: Build
+ run: cargo build --locked --release
+ - name: Verify capture/restore
+ run: ./target/release/shuttle-runner verify guests/out/bios.wasm guests/out/os.wasm guests/out/child.wasm
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index b7de367..e00ef80 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -6,17 +6,17 @@
-
-
-
-
+
+
+
+
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
index dc1a9f4..58d834c 100644
--- a/.idea/encodings.xml
+++ b/.idea/encodings.xml
@@ -3,13 +3,13 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CLAUDE.md b/CLAUDE.md
index 74cf7b9..631e4cf 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -23,3 +23,36 @@
## Branches
- New features: `feature/`
- Fixes: `fix/`
+
+## Language
+- Talk to the user in Japanese.
+- Files in the repository (code, comments, docs, commit messages) are
+ written in English unless the user says otherwise.
+
+# Project
+
+Shuttle is a WebAssembly runtime library (Java binding + native Rust:
+Wasmtime + Weave) for Minecraft computer mods. It is not a mod itself.
+OpenComputers will use it through an addon; a new Retro Computing mod
+will include it by default. Current status and open decisions:
+`native/HANDOFF.md`. Overview and docs: `README.md`, `docs/`.
+
+| Path | What |
+| --- | --- |
+| `native/` | Rust workspace: `shuttle-core`, `shuttle-runner`, C test guests |
+| `shuttle-java/` | `jp.akkiserver.shuttle`: Wasmtime + Weave JNI binding |
+| `shuttle-java-example/` | Weave example |
+| `shuttle-wit-parser/`, `shuttle-wit-maven-plugin/` | WIT tooling |
+
+## Build and test (Windows)
+- Native core: `.\native\scripts\build.ps1` (needs LLVM for clang and
+ wasm-ld). Do not redirect its output; under Windows PowerShell 5.1 that
+ makes cargo's progress output abort the script.
+- Maven: there is no `mvn` on PATH. Use IntelliJ's bundled Maven,
+ `%LOCALAPPDATA%\Programs\IntelliJ IDEA\plugins\maven-plugin\lib\maven3\bin\mvn.cmd`,
+ with `JAVA_HOME=%USERPROFILE%\.jdks\temurin-25.0.4` (the global
+ `JAVA_HOME` points at `bin\` and is wrong), and the profile
+ `-P build-host-windows-x86_64`. The default profile uses cross + Docker.
+- JNI class names are also written as strings in Rust (`java_type = "..."`).
+ After renaming Java classes or packages, grep the `.rs` files and run the
+ tests: a wrong name compiles but fails at run time.
diff --git a/README.md b/README.md
index 45a6d96..d50efd2 100644
--- a/README.md
+++ b/README.md
@@ -1,565 +1,74 @@
-[](https://github.com/StefanRichterHuber/wasmtime-ng/actions/workflows/maven.yml)
+[](https://github.com/akkiserver-dev/shuttle/actions/workflows/native.yml)
-# Wasmtime-java-ng
+# Shuttle
-> **Disclaimer:** This project is an independent community effort and is not affiliated with, maintained, or endorsed by the original [Wasmtime project](https://github.com/bytecodealliance/wasmtime) or the Bytecode Alliance.
+> **Status:** early prototype. The native core runs headless only; no mod uses Shuttle yet.
-> **AI Disclaimer:** Parts of this project were vibe-coded or written with the help of an AI assistant (Claude); the remaining parts were written by hand.
-
-Wasmtime-java-ng provides Java bindings for [Wasmtime](https://github.com/bytecodealliance/wasmtime), a fast, secure, and highly configurable WebAssembly runtime.
-
-This project allows Java applications to execute WebAssembly modules, interact with linear memory, and leverage the WebAssembly System Interface (WASI) through a native bridge built on Rust.
-
-## Project Structure
-
-This repository is a Maven multi-module reactor. As of this version, `wasmtimejavang` is a **core** artifact (Wasmtime primitives, the Component Model plumbing, WASI Preview 1) plus one artifact per WASI Preview 2 "world" — so a consumer only pulls in the WASI Preview 2 support they actually use, instead of everything at once:
-
-* **[`wasmtimejavang`](wasmtimejavang)** — the core artifact everyone depends on: `WasmtimeEngine`/`WasmtimeModule`/`WasmtimeStore`/`WasmtimeInstance`/`WasmtimeLinker`/`WasmtimeMemory`/`WasmtimeFunction`, the Component Model plumbing (`WasmtimeComponent`, `WasmtimeComponentLinker`, `WasmtimeComponentInstance`, `WasmComponentContext`), the native Rust bridge (all platforms), and WASI Preview 1 (`WasiPI1Context`, WASI threads). See [Usage](#usage) below.
-* **[`wasmtimejavang-wasi-io`](wasmtimejavang-wasi-io)** — WASI Preview 2 `wasi:io/poll,streams,error`. Nearly every other WASI Preview 2 artifact depends on this one, since it owns the shared stream/pollable tables they hand resources out of.
-* **[`wasmtimejavang-wasi-clocks`](wasmtimejavang-wasi-clocks)**, **[`wasmtimejavang-wasi-random`](wasmtimejavang-wasi-random)**, **[`wasmtimejavang-wasi-cli`](wasmtimejavang-wasi-cli)**, **[`wasmtimejavang-wasi-filesystem`](wasmtimejavang-wasi-filesystem)**, **[`wasmtimejavang-wasi-sockets`](wasmtimejavang-wasi-sockets)**, **[`wasmtimejavang-wasi-http`](wasmtimejavang-wasi-http)** — one artifact per remaining WASI Preview 2 "world", each depending on `wasmtimejavang-wasi-io` where it needs shared streams/pollables (all but `wasi-random`, which needs nothing beyond core).
-* **[`wit-parser`](wit-parser)** and **[`wit-codegen-maven-plugin`](wit-codegen-maven-plugin)** — build-time-only tooling, not a runtime dependency of anything above. Together they let a *consuming* project (or, as of this version, every one of this repository's own WASI Preview 2 artifacts) generate typed, Java component-context interfaces straight from a WIT file instead of hand-writing them — see [Implementing your own component context](#implementing-your-own-component-context).
-
-Pick only the artifacts you need: depending on `wasmtimejavang` alone gets you core-wasm + WASI Preview 1, with no Component Model support at all; add whichever `wasmtimejavang-wasi-*` artifacts you actually need (each transitively pulls in `wasmtimejavang-wasi-io` where required) — e.g. just `wasmtimejavang-wasi-http` for an outgoing-HTTP-only guest, or `wasmtimejavang-wasi-cli` + `wasmtimejavang-wasi-filesystem` for a sandboxed-filesystem CLI tool.
-
-## Prerequisites
-
-Building this project requires both Rust and Java toolchains:
-
-* **Java 21+** and **Maven**
-* **Rust** and **Cargo**
-* **[cross-rs](https://github.com/cross-rs/cross)** and **Docker** (required for cross-platform native builds)
+Shuttle is a WebAssembly runtime library for Minecraft computer mods. It is not a mod itself: computer mods use it to run the programs on their in-game computers. Two consumers are planned: OpenComputers (OC), through a separate addon mod, and a new Retro Computing mod, which will include Shuttle by default. Neither integration exists yet.
-## Building
+On behalf of the host mod, Shuttle runs a stack of Wasm guests:
-The project uses Maven to orchestrate both the Java compilation and the Rust native build. To build the entire project for Linux x86_64:
-
-```shell
-mvn clean install
```
-
-### Multi-Platform Builds
-
-By default, only the native library for Linux x86_64 is built. You can target specific platforms using Maven profiles:
-
-* `build-linux-x86-64`: Linux x86_64
-* `build-linux-aarch64`: Linux Arm64
-* `build-windows-x86_64`: Windows x86_64
-
-To build a JAR containing native libraries for all supported platforms:
-
-```shell
-mvn -P build-linux-x86-64,build-linux-aarch64,build-windows-x86_64 clean install
+firmware implemented by Shuttle's native core
+ └─ BIOS player-made Wasm (for example stored on an EEPROM item)
+ └─ OS kernel player-made Wasm, started by the BIOS with `exec`
+ └─ child programs started by the kernel with `co_create`
```
-For production environments, the `release` profile optimizes the Rust binaries for performance and size:
+The firmware talks to guests only through the **firmware ABI** (`fw.*`, `co.*`, `sys.call`). Device calls look like OpenComputers method calls, either synchronous (`dev_call`) or asynchronous (`dev_submit` + a completion event). The ABI is specified in a separate draft document (draft 0.1, not published in this repository); [`native/README.md`](native/README.md) lists which parts of it are implemented.
-```shell
-mvn -P release,build-linux-x86-64,build-linux-aarch64,build-windows-x86_64 clean install
-```
+## How it works
-## Usage
+- **Runtime:** [Wasmtime](https://github.com/bytecodealliance/wasmtime), made checkpointable with [Weave](https://github.com/akkiserver-dev/weave) (branch `shuttle-host-hooks`). Weave transforms a Wasm module so that a running program can be unwound into its linear memory and rebuilt later. This lets a host mod save a computer with the world and continue it mid-execution after a reload.
+- **Native core (Rust, `native/`):** Wasmtime, Weave, the firmware ABI, the `co_*` machinery, memory limits and snapshot capture. Today it is driven by the headless `shuttle-runner`.
+- **Java binding (`shuttle-java`):** Java API over Wasmtime and Weave through JNI, for the host mod to call. A high-level Machine API on top of the native core is planned but not written yet.
+- **Targets:** Windows, macOS and Linux, each on x86_64 and arm64.
-Include the core dependency into your project `pom.xml` (the published artifact contains native libraries for Linux x86_64, Linux aarch64 and Windows x86_64):
+## Repository layout
-```xml
-
- io.github.stefanrichterhuber
- wasmtimejavang
- [Current Version]
-
-```
+| Path | What |
+| --- | --- |
+| [`native/`](native) | Shuttle native core (Rust): `shuttle-core`, the headless `shuttle-runner`, and C test guests in `native/guests`. See [`native/README.md`](native/README.md). |
+| [`shuttle-java/`](shuttle-java) | Java bindings for Wasmtime plus the Weave (checkpointable Wasm) JNI layer. See [docs/java-binding.md](docs/java-binding.md). |
+| [`shuttle-java-example/`](shuttle-java-example) | Runnable Weave example: run a guest a few "ticks" at a time, snapshot it, restore it and continue. |
+| [`shuttle-wit-parser/`](shuttle-wit-parser), [`shuttle-wit-maven-plugin/`](shuttle-wit-maven-plugin) | Build-time WIT tooling: generate Java component-context interfaces from WIT files. See [docs/wit-codegen.md](docs/wit-codegen.md). |
+| [`docs/`](docs) | Documentation. Start at [docs/README.md](docs/README.md). |
-This alone gets you core-wasm execution and WASI Preview 1 — no WASI Preview 2 / Component Model support. To run `wasm32-wasip2` components, add the WASI Preview 2 artifact(s) you actually need (see [Project Structure](#project-structure) above), e.g. for `wasi:http`:
+The Java modules form one Maven reactor: groupId `jp.akkiserver`, parent artifactId `shuttle`, one artifact per directory (`shuttle-java`, `shuttle-java-example`, `shuttle-wit-parser`, `shuttle-wit-maven-plugin`). The version is set in the root [`pom.xml`](pom.xml).
-```xml
-
- io.github.stefanrichterhuber
- wasmtimejavang-wasi-http
- [Current Version]
-
-```
+## Quick start
-### Function Imports
+Native core (requires Rust, plus clang and wasm-ld for the C guests):
-You can import Java methods into the WebAssembly context, allowing WASM modules to invoke native Java code.
-
-```java
-String wat = """
- (module
- (func $hello (import "env" "hello"))
- (func (export "run") (call $hello))
- )
- """;
-
-try (
- WasmtimeEngine engine = new WasmtimeEngine();
- WasmtimeModule module = new WasmtimeModule(engine, wat);
- WasmtimeStore store = new WasmtimeStore(engine);
- WasmtimeLinker linker = new WasmtimeLinker(engine, store)
-) {
- store.getContext().put("greeting", "Hello world");
-
- // Define a Java function for the WASM module to call
- linker.importFunction("env", "hello", List.of(), List.of(), (instance, params) -> {
- System.out.println("Java side: " + instance.getContext().get("greeting"));
- return new Object[] {};
- });
-
- try (WasmtimeInstance instance = new WasmtimeInstance(store, module, linker)) {
- instance.invoke("run");
- }
-}
+```powershell
+.\native\scripts\build.ps1 # Windows: build guests + workspace, then verify
```
-
-### Supported WASM Types
-
-All type conversions are bi-directional if not mentioned otherwise.
-
-| Wasm type | Java type | Comment |
-| :--- | :--- | :---: |
-| **`I32`** | `java.lang.Integer` | Numbers are always boxed |
-| **`I64`** | `java.lang.Long` | Numbers are always boxed |
-| **`F32`** | `java.lang.Float` | Numbers are always boxed |
-| **`F64`** | `java.lang.Double` | Numbers are always boxed |
-| **`V128`** | `io.github.stefanrichterhuber.wasmtimejavang.V128` | V128 is a wrapper around `byte[16]`. Build in conversion from /to `byte[]`, `short[]`, `int[]`, `long[]` and `BigInteger` |
-| **`FuncRef`** | `io.github.stefanrichterhuber.wasmtimejavang.WasmtimeFunction` | Callable function reference. Currently export from wasm to java supported |
-| **`ExternRef`** | any java object | Any java object can be passed through the wasm runtime as extern ref |
-
-### Memory Interaction
-
-The `WasmtimeMemory` class provides efficient access to the WASM linear memory by mapping it directly to a `java.nio.ByteBuffer`.
-
-```java
-try (WasmtimeInstance instance = new WasmtimeInstance(store, module, linker)) {
- WasmtimeMemory mem = instance.getMemory("memory");
-
- // Read 4 bytes from offset 0x1000
- byte[] data = mem.read(0x1000, 4);
-
- // Write a single byte to offset 0x1003
- mem.write(0x1003, (byte) 5);
-
- // Higher level memory interactions. Usually one would call some kind of native alloc
- // function to allocate memory locations prior to use
- mem.writeCString(0x1000, "Hello", StandardCharsets.UTF_8);
- assertEquals("Hello", mem.readCString(0x1000, StandardCharsets.UTF_8));
-
- mem.writeString(0x1000, "World", StandardCharsets.UTF_8);
- assertEquals("World", mem.readString(0x1000, "World".getBytes().length, StandardCharsets.UTF_8));
-
- mem.writeLong(0x1000, Long.MAX_VALUE);
- assertEquals(Long.MAX_VALUE, mem.readLong(0x1000));
-
- mem.writeInt(0x1000, Integer.MAX_VALUE);
- assertEquals(Integer.MAX_VALUE, mem.readInt(0x1000));
-
- mem.writeShort(0x1000, Short.MAX_VALUE);
- assertEquals(Short.MAX_VALUE, mem.readShort(0x1000));
-
- mem.write(0x1000, Byte.MAX_VALUE);
- assertEquals(Byte.MAX_VALUE, mem.readByte(0x1000));
-
- // Grow to memory to have another page
- mem.grow(1);
-}
+```sh
+./native/scripts/build.sh # Linux / WSL2
```
-## WASI Snapshot Preview 1
+`verify` boots BIOS → OS → child, then repeats the run with capture → restore into a fresh machine at every tick boundary and checks that the logs are identical.
-While Wasmtime provides built-in WASI support, this library implements WASI Preview 1 directly on the Java side (`WasiPI1Context`). This approach reduces indirection and allows WASM modules to interact directly with Java objects, such as `InputStream`, `OutputStream`, and virtual filesystems.
+Java bindings on Windows (requires JDK 21, Maven and Rust; no Docker):
-### Virtualized Filesystem
-
-To protect the host system, you can use a virtual filesystem like [JimFS](https://github.com/google/jimfs) to provide a sandboxed environment for the WASM module.
-
-```java
-try (FileSystem fs = Jimfs.newFileSystem(Configuration.unix())) {
- Path root = fs.getPath("/");
- Files.writeString(root.resolve("input.txt"), "Data for WASM");
-
- WasiPI1Context wasi = new WasiPI1Context()
- .withDirectory(root, ".") // Map virtual root to WASM current directory
- .withStdOut(System.out);
-
- linker.link(wasi);
-
- try (WasmtimeInstance instance = new WasmtimeInstance(store, module, linker)) {
- instance.start();
- }
-}
-```
-
-### Supported WASI Functions
-
-| Category | Functions | Status | Configuration |
-| :--- | :--- | :---: | :--- |
-| **Arguments** | `args_get`, `args_sizes_get` | ✅ | `withArguments(List)` |
-| **Environment** | `environ_get`, `environ_sizes_get` | ✅ | `withEnvs(Map)` |
-| **Clocks** | `clock_res_get`, `clock_time_get` | ✅ | `withClock(Clock)` |
-| **Random** | `random_get` | ✅ | `withRandom(Random)` |
-| **Process** | `proc_exit` | ✅ | Throws `ProcExitException` |
-| | `proc_raise` | 🟡 | Returns `NOSYS` (Stub) |
-| **Scheduler** | `sched_yield` | ✅ | Calls `Thread.yield()` |
-| **Filesystem** | Full FD and Path operations | ✅ | `withDirectory(Path, String)` |
-| **Network** | `sock_accept`, `sock_recv`, `sock_send`, `sock_shutdown` | ✅ | `withSocket()`, `withServerSocket()` |
-| **Polling** | `poll_oneoff` | 🟡 | Clock events supported |
-
-### Configuration Options
-
-`WasiPI1Context` provides a fluent API for resource virtualization:
-
-* **Standard I/O**: `withStdIn(InputStream)`, `withStdOut(OutputStream)`, `withStdErr(OutputStream)`.
-* **Filesystem**: `withDirectory(Path host, String client)` to map host/virtual paths.
-* **Networking**:
- * `withSocket(java.net.Socket)`: Pre-opens a client socket.
- * `withServerSocket(java.net.ServerSocket)`: Pre-opens a listening socket.
-
-### WASI threads
-
-There is some preliminary support for WASI threads using the additional context `WasiThreadContext`.
-
-```java
- try (FileInputStream fis = new FileInputStream(wasmPath.toFile());
- WasmtimeEngine engine = new WasmtimeEngine();
- WasmtimeModule module = new WasmtimeModule(engine, fis);
- WasmtimeStore store = new WasmtimeStore(engine);
- WasmtimeLinker linker = new WasmtimeLinker(engine, store);
- // A shared memory is necessary to share memory between threads
- WasmtimeSharedMemory sharedMemory = new WasmtimeSharedMemory(engine, 2, 256)) {
-
- WasiPI1Context wasiContext = new WasiPI1Context()
- .withArguments(List.of("wasip1threadtest"))
- .withStdOut(System.out)
- .withStdErr(System.err);
-
- WasiThreadContext threadContext = new WasiThreadContext();
-
- linker.defineSharedMemory("env", "memory", sharedMemory);
- linker.link(wasiContext);
- linker.link(threadContext);
-
- try (WasmtimeInstance instance = new WasmtimeInstance(store, module, linker)) {
- instance.start();
- }
-
- // Give some time for threads to finish and print
- Thread.sleep(1000);
-}
-
-```
-
-> **Info:** As of now, nightly toolchain is required to properly build rust wasm wasip1 apps with thread support!
-
-## WASI Preview 2 / Component Model (`wasm32-wasip2`)
-
-Alongside core-module WASI Preview 1, this library supports the [Component Model](https://component-model.bytecodealliance.org/) (`wasm32-wasip2` binaries) — again with WASI implemented on the Java side rather than delegated to `wasmtime-wasi`. It's built from three pieces:
-
-* `WasmtimeComponent` — compiles a component, and can introspect it *without instantiating it*: `getImportInterfaces()` / `getExportInterfaces()` read the interfaces a component needs/exports straight from the compiled binary, and `isCommand()` reports whether it exports `wasi:cli/run` (i.e. is runnable as a command, as opposed to e.g. a service with no entry point).
-* `WasmtimeComponentLinker` — resolves imports, mirroring `WasmtimeLinker` but for the richer Component Model value/resource model.
-* `WasmtimeComponentInstance` — an instantiated component; exported functions are invoked dynamically by interface + function name via `invoke(...)`, or via the `asCliRunnable()` convenience for a `wasi:cli/run` command.
-
-```java
-try (
- FileInputStream fis = new FileInputStream("hello.wasm");
- WasmtimeEngine engine = new WasmtimeEngine();
- WasmtimeComponent component = new WasmtimeComponent(engine, fis);
- WasmtimeStore store = new WasmtimeStore(engine);
- WasmtimeComponentLinker linker = new WasmtimeComponentLinker(engine, store)
-) {
- // Link an explicitly-configured context (captures stdout)...
- ByteArrayOutputStream stdout = new ByteArrayOutputStream();
- linker.linkContext(new WasiCliContext().withStdOut(stdout).withArguments(List.of("hello")));
-
- // ...then auto-link whatever else the component actually needs (wasi:clocks,
- // wasi:random, ...), discovered straight off the compiled component.
- linker.linkRequired(component);
-
- try (WasmtimeComponentInstance instance = new WasmtimeComponentInstance(store, component, linker)) {
- instance.asCliRunnable().call(); // invokes wasi:cli/run#run
- }
-}
+```powershell
+mvn -P build-host-windows-x86_64 -pl shuttle-java,shuttle-java-example -am install
+mvn -pl shuttle-java-example exec:java
```
-`linkRequired` only fills in interfaces nothing has already been linked for, so `linkContext(...)` calls made beforehand always take precedence — this is also the mechanism for overriding built-in behavior (see below).
+The second command runs the Weave example. Linux builds, the other Maven profiles, CI and known problems are covered in [docs/building.md](docs/building.md).
-### Supported WASI Preview 2 interfaces
+## Documentation
-Each interface group lives in its own `WasmComponentContext` implementation under the `wasip2` package, each in its own Maven artifact, and all seven are auto-discovered by `linkRequired(...)` with zero configuration as long as their artifact is on the classpath — see [Project Structure](#project-structure).
+- [docs/building.md](docs/building.md): building the native core and the Maven modules, CI, requirements, known issues
+- [docs/java-binding.md](docs/java-binding.md): the `shuttle-java` API (Wasmtime and Weave) and its architecture
+- [docs/wit-codegen.md](docs/wit-codegen.md): generating Java component contexts from WIT
+- [native/README.md](native/README.md): the native core prototype, its results and its ABI coverage
-| Interfaces | Class | Artifact | Status | Configuration |
-| :--- | :--- | :--- | :---: | :--- |
-| `wasi:cli/environment,exit,stdin,stdout,stderr,terminal-*` | `WasiCliContext` | `wasmtimejavang-wasi-cli` | ✅ | `withEnvs(Map)`, `withArguments(List)`, `withStdIn/StdOut/StdErr(...)`. `terminal-*` always reports "not a tty" |
-| `wasi:clocks/monotonic-clock,wall-clock` | `WasiClocksContext` | `wasmtimejavang-wasi-clocks` | ✅ | none (uses `System.nanoTime()`/`System.currentTimeMillis()`) |
-| `wasi:random/random,insecure,insecure-seed` | `WasiRandomContext` | `wasmtimejavang-wasi-random` | ✅ | `withSecureRandom(Random)`, `withRandom(Random)` (the insecure generator) |
-| `wasi:io/poll,streams,error` | `WasiIoContext` | `wasmtimejavang-wasi-io` | ✅ | Owns the shared stream/pollable tables other contexts (`WasiCliContext`, `WasiClocksContext`, `WasiFilesystemContext`, `WasiSocketsContext`, `WasiHttpContext`) depend on. Both blocking and non-blocking reads/skips are implemented (`[method]input-stream.{blocking-,}read`/`{blocking-,}skip`, the batch `poll` function, `[method]pollable.ready`) |
-| `wasi:filesystem/types,preopens` | `WasiFilesystemContext` | `wasmtimejavang-wasi-filesystem` | ✅ | `withDirectory(Path host, String client)` (repeatable). No symlink support by design: `symlink-at`/`readlink-at` report `unsupported`, and `path-flags.symlink-follow` is ignored — host paths always resolve following symlinks |
-| `wasi:sockets/network,instance-network,tcp(-create-socket),udp(-create-socket),ip-name-lookup` | `WasiSocketsContext` | `wasmtimejavang-wasi-sockets` | ✅ | None — a guest creates/binds/connects/listens its own sockets, same as on a real host. IPv4 only (`ipv6` rejected as `not-supported`, `[method]{tcp,udp}-socket.address-family` always reports `ipv4`). Some options `java.net` has no equivalent for (TCP `hop-limit`, TCP keep-alive idle-time/interval/count, UDP `unicast-hop-limit`) are stored and returned as configured but not actually applied to the OS socket |
-| `wasi:http/types,outgoing-handler` | `WasiHttpContext` | `wasmtimejavang-wasi-http` | 🟡 | None — a guest makes outgoing HTTP calls (`fetch()`-style) via a real, blocking `java.net.http.HttpClient` call. `request-options#between-bytes-timeout` has no `HttpClient` equivalent (stored but not applied). `wasi:http/incoming-handler` (a guest *exporting* a request handler, for reverse-proxy-style hosting) isn't implemented yet |
+## Credits and license
-`WasiFilesystemContext` mirrors WASI Preview 1's filesystem support (`WasiPI1Context` + `withDirectory`): preopened host directories are exposed as `descriptor` resources sandboxed the same way -- a guest path can never resolve outside its preopened directory, no matter how many `..` segments it contains. Reads/writes go through `wasi:io/streams` the same way `wasi:cli/stdout` does: `[method]descriptor.read-via-stream`/`write-via-stream`/`append-via-stream` hand out `input-stream`/`output-stream` resources from the shared `wasi-io` table (so it depends on `"wasi-io"` the same way `WasiCliContext` and `WasiClocksContext` do), rather than reading/writing bytes directly.
+The Java Wasmtime bindings in this repository are a fork of [StefanRichterHuber/wasmtime-ng](https://github.com/StefanRichterHuber/wasmtime-ng) (MIT, © 2024 Stefan Richter-Huber). See [LICENSE](LICENSE). Upstream names these earlier Java bindings as prior work: [bluejekyll/wasmtime-java](https://github.com/bluejekyll/wasmtime-java) and [kawamuray/wasmtime-java](https://github.com/kawamuray/wasmtime-java).
-```java
-try (FileSystem fs = Jimfs.newFileSystem(Configuration.unix().toBuilder()
- .setAttributeViews("basic", "owner", "posix", "unix").build())) {
- Path root = fs.getPath("/");
- Files.writeString(root.resolve("input.txt"), "Data for WASM");
-
- linker.linkContext(new WasiCliContext().withStdOut(System.out));
- linker.linkContext(new WasiFilesystemContext().withDirectory(root, ".")); // preopen "." -> root
- linker.linkRequired(component);
-
- try (WasmtimeComponentInstance instance = new WasmtimeComponentInstance(store, component, linker)) {
- instance.asCliRunnable().call();
- }
-}
-```
-
-Unlike `WasiFilesystemContext`, `WasiSocketsContext` needs no configuration at all: WASI Preview 2 gives a guest full control over its own sockets (create, bind, connect/listen, accept, send/receive), so there's no host-side preopen step to wire up -- linking it is enough, and a guest creating a TCP or UDP socket behaves exactly as it would talking to a real OS network stack (loopback and outbound connections both work). The two-phase `start-x`/`finish-x` operations WASI defines for non-blocking bind/connect/listen are collapsed into one blocking `java.net` call apiece internally, since every host call in this bridge is already synchronous from the guest's perspective.
-
-```java
-linker.linkContext(new WasiCliContext().withStdOut(System.out));
-linker.linkContext(new WasiSocketsContext());
-linker.linkRequired(component); // pulls in wasi-io, wasi-clocks, etc. as needed
-```
-
-`WasiHttpContext` likewise needs no configuration: `wasi:http/outgoing-handler` lets a guest make outbound HTTP requests, and every call is dispatched via a real `java.net.http.HttpClient`, synchronously, since (as with `WasiSocketsContext`'s two-phase operations) every host call in this bridge is already synchronous from the guest's perspective — there's no genuine asynchrony to preserve, so `future-incoming-response`/`future-trailers` are always already resolved by the time the guest polls them. `wasi:http` isn't part of `wasm32-wasip2`'s built-in WASI componentization the way `wasi:cli`/`wasi:clocks`/`wasi:sockets` are, so a guest needs `wit_bindgen::generate!` against the `wasi:http` WIT package to consume it (see [Implementing your own component context](#implementing-your-own-component-context) below for the general mechanism).
-
-```java
-linker.linkContext(new WasiCliContext().withStdOut(System.out));
-linker.linkContext(new WasiHttpContext());
-linker.linkRequired(component); // pulls in wasi-io, wasi-clocks, wasi-random
-```
+> **Disclaimer:** This project is an independent community effort and is not affiliated with, maintained, or endorsed by the [Wasmtime project](https://github.com/bytecodealliance/wasmtime), the Bytecode Alliance, Mojang or Microsoft.
-### Implementing your own component context
-
-This is the mechanism that matters most for actually using this library: WASI is just the *built-in* set of `WasmComponentContext` implementations, and any Java application can define its own to expose custom host functionality — database access, business logic, whatever — to a component's imports, the exact same way. There are two ways to implement one: hand-write it directly against `WasmComponentContext` (full control, shown first below), or generate a typed interface from a WIT file via `wit-codegen-maven-plugin` and implement just the typed methods (less boilerplate — no manual version-string wiring or `Object[]` casts). Both are shown below using the same example (its full source is in the repo and covered by `WasmtimeCustomComponentTest`, so it's guaranteed to stay in sync with the code, not just aspirational documentation).
-
-**1. Define the interface in WIT.** This is the contract the guest and the host agree on — a Java-side `WasmComponentContext` needs no WIT file itself (it's built against the fully dynamic `component::Val` API), but the *component being compiled* does, since Rust needs it to generate typed bindings. [`wasmtimejavang-wasi-cli/src/test/rust/wasip2customtest/wit/world.wit`](wasmtimejavang-wasi-cli/src/test/rust/wasip2customtest/wit/world.wit):
-
-```wit
-package my:custom@1.0.0;
-
-interface greet {
- hello: func(name: string) -> string;
- add: func(a: u32, b: u32) -> u32;
-}
-
-world custom-world {
- import greet;
-}
-```
-
-**2. Consume it from the guest.** Any language with Component Model tooling works; the test fixture is Rust using [`wit-bindgen`](https://github.com/bytecodealliance/wit-bindgen) (one of two test fixtures in this repo that need it, alongside `wasi:http`'s — every WASI-only fixture otherwise relies solely on `wasm32-wasip2`'s built-in componentization). [`wasmtimejavang-wasi-cli/src/test/rust/wasip2customtest/src/main.rs`](wasmtimejavang-wasi-cli/src/test/rust/wasip2customtest/src/main.rs):
-
-```rust
-wit_bindgen::generate!({
- world: "custom-world",
- path: "wit",
-});
-
-use my::custom::greet;
-
-fn main() {
- let greeting = greet::hello("Wasmtime-Java");
- println!("GREETING={}", greeting);
-
- let sum = greet::add(19, 23);
- println!("SUM={}", sum);
-}
-```
-
-**3. Implement it in Java.** Two ways — hand-write it directly against `WasmComponentContext`, or generate a typed interface from the same WIT file and implement that instead.
-
-**Hand-written**, to provide `hello`/`add` directly. [`GreetComponentContext`](wasmtimejavang-wasi-cli/src/test/java/io/github/stefanrichterhuber/wasmtimejavang/GreetComponentContext.java):
-
-```java
-public class GreetComponentContext implements WasmComponentContext {
- private static final String INTERFACE = "my:custom/greet";
-
- private SemanticVersion version = new SemanticVersion(1, 0, 0);
-
- @Override
- public String name() { return "greet"; } // stable id, referenced by getDependencies()
-
- @Override
- public List getImportFunctions() {
- String versioned = INTERFACE + "@" + version;
- return List.of(
- new ComponentImportFunction(versioned, "hello", this::hello),
- new ComponentImportFunction(versioned, "add", this::add));
- }
-
- @Override
- public List getImportResources() { return List.of(); }
-
- @Override
- public Set getProvidedInterfaces() { return Set.of(INTERFACE); } // see "Auto-discovery" below
-
- private Object[] hello(WasmtimeComponentInstance instance, Object... args) {
- return new Object[] { "Hello, " + (String) args[0] + "!" };
- }
-
- private Object[] add(WasmtimeComponentInstance instance, Object... args) {
- return new Object[] { (Integer) args[0] + (Integer) args[1] };
- }
-
- @Override
- public WasmComponentContext withVersion(SemanticVersion version) { this.version = version; return this; }
-
- @Override
- public SemanticVersion getVersion() { return version; }
-}
-```
-
-Argument/return types follow the value bridge described above — here just `String` and `Integer` (WIT `u32`), but the same context could just as well take/return a `Map` (`record`), `List`/`Object[]` (`list`/`tuple`), `byte[]` (`list`), or a resource.
-
-**Or generated**, via `wit-codegen-maven-plugin`'s `generate-wit-sources` goal (bound to `generate-sources` by default). Add the plugin to your `pom.xml`, pointing `witSourceDirectory` at a directory containing the same `greet.wit` from step 1 (every `.wit` file directly in it is generated from independently):
-
-```xml
-
- io.github.stefanrichterhuber
- wit-codegen-maven-plugin
- [Current Version]
-
-
-
- generate-wit-sources
-
-
- ${project.basedir}/src/main/wit
- com.example.generated
-
-
-
-
-```
-
-This generates `com.example.generated.GreetContext` — an **interface**, not an abstract class (so a class can implement several generated interfaces at once — see "Combining interfaces" below), extending `WasmComponentContext` with `getImportFunctions()`/`getImportResources()`/`getProvidedInterfaces()`/`name()` already wired as `default` methods and one typed abstract method per WIT function (see [`WitCodeGenerator`](wit-codegen-maven-plugin/src/main/java/io/github/stefanrichterhuber/witcodegen/WitCodeGenerator.java) for the full WIT-to-Java type mapping). Implement it directly instead of hand-rolling `WasmComponentContext`:
-
-```java
-public class MyGreetContext implements com.example.generated.GreetContext {
- @Override
- public String hello(WasmtimeComponentInstance instance, String name) {
- return "Hello, " + name + "!";
- }
-
- @Override
- public int add(WasmtimeComponentInstance instance, int a, int b) {
- return a + b;
- }
-}
-```
-
-To generate from a real, multi-file WIT **world** instead of one interface (a package directory, optionally with a `deps/` subfolder) — e.g. to regenerate the scaffolding this project's own `Wasi*Context` classes are built on — configure `worldSourceDirectory` + `worldName` instead of (or alongside) `witSourceDirectory`:
-
-```xml
-
- ${project.basedir}/src/main/wit/wasi-cli/wit
- command
- com.example.generated.wasicli
-
-```
-
-This generates one interface per interface the world's imports flatten to (27 for `wasi:cli`'s `command` world). Exports are intentionally not modeled — `WasmComponentContext` only represents host-provided imports; call an export dynamically via `WasmtimeComponentInstance.invoke(...)` instead.
-
-**Combining interfaces.** Implementing several generated interfaces on one class (useful when they share state, e.g. two interfaces that both hand out a resource type minted by a shared table — this is exactly what every built-in `Wasi*Context` does) requires overriding `getImportFunctions()`/`getImportResources()`/`getProvidedInterfaces()` to combine each interface's own `default` implementation — Java requires this override whenever two implemented interfaces disagree on a `default` method's body, which any two distinct generated interfaces always do:
-
-```java
-public class MyBundledContext implements FooContext, BarContext {
- @Override
- public List getImportFunctions() {
- List result = new ArrayList<>();
- result.addAll(FooContext.super.getImportFunctions());
- result.addAll(BarContext.super.getImportFunctions());
- return result;
- }
- // ...same pattern for getImportResources()/getProvidedInterfaces(); withVersion()/getVersion()
- // stay this class's own responsibility (a plain field) since interfaces can't hold state.
-}
-```
-
-**Note:** the plugin can't be wired into `wasmtimejavang` (the core artifact) itself this way — that would be a circular module dependency within this repository's reactor, since the plugin's own tests depend on core. Every one of this repository's own WASI Preview 2 artifacts (`wasmtimejavang-wasi-io`, `-wasi-clocks`, `-wasi-random`, `-wasi-cli`, `-wasi-filesystem`, `-wasi-sockets`, `-wasi-http`) demonstrates the pattern instead: a *separate* artifact depending on core, using the plugin as a normal `generate-sources` build step (see e.g. [`wasmtimejavang-wasi-http/pom.xml`](wasmtimejavang-wasi-http/pom.xml)). Your own project, depending on any of these artifacts as an ordinary library, never has this constraint. `wit-parser`, a JNI binding around the [`wit-parser`](https://crates.io/crates/wit-parser) Rust crate, handles the actual WIT parsing behind the plugin.
-
-**4. Link it and run.** (identical either way — only the implementing class differs). [`WasmtimeCustomComponentTest`](wasmtimejavang-wasi-cli/src/test/java/io/github/stefanrichterhuber/wasmtimejavang/WasmtimeCustomComponentTest.java):
-
-```java
-try (FileInputStream fis = new FileInputStream(wasmPath);
- WasmtimeEngine engine = new WasmtimeEngine();
- WasmtimeComponent component = new WasmtimeComponent(engine, fis);
- WasmtimeStore store = new WasmtimeStore(engine);
- WasmtimeComponentLinker linker = new WasmtimeComponentLinker(engine, store)) {
-
- linker.linkContext(new WasiCliContext().withStdOut(System.out));
- linker.linkContext(new GreetComponentContext());
- linker.linkRequired(component); // pulls in wasi-io for stdout; nothing to do for "my:custom/greet"
-
- try (WasmtimeComponentInstance instance = new WasmtimeComponentInstance(store, component, linker)) {
- instance.asCliRunnable().call(); // prints "GREETING=Hello, Wasmtime-Java!" / "SUM=42"
- }
-}
-```
-
-`linker.linkContext(...)` links a context explicitly and unconditionally; `linkRequired(component)` only fills in interfaces *nothing has already claimed* (see above), so the two compose freely — explicit calls always take precedence.
-
-**Depending on another context.** To share state with another context (e.g. `WasiIoContext`'s stream table), declare it by bare name in `getDependencies()` and resolve it in `onDependenciesResolved(ComponentContextLookup)` — `WasmtimeComponentLinker` links declared dependencies first and guarantees they're available by the time it's called:
-
-```java
-@Override
-public List getDependencies() { return List.of(WasiIoContext.NAME); }
-
-@Override
-public void onDependenciesResolved(ComponentContextLookup lookup) {
- this.io = (WasiIoResources) lookup.resolve(WasiIoContext.NAME, getVersion()).orElseThrow();
-}
-```
-
-**Auto-discovery.** To make a context discoverable by `linkRequired(component)` instead of linking it explicitly, declare the (bare, version-independent) interface names it implements via `getProvidedInterfaces()` (as `GreetComponentContext` already does above — a generated interface has this pre-wired as a `default` method already, so a generated-based implementer gets it for free) and register the class as a `WasmComponentContext` [`ServiceLoader`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/ServiceLoader.html) provider (a `META-INF/services/io.github.stefanrichterhuber.wasmtimejavang.WasmComponentContext` file listing the class, which needs a public no-arg constructor or a public static `provider()` method) — this is exactly how the seven built-in `Wasi*Context` classes register themselves.
-
-**Watch out:** `getProvidedInterfaces()` must be overridden (or, for a generated interface, already resolve correctly) even for a context that's *always* linked explicitly via `linkContext(...)` and never registered for `ServiceLoader` discovery — if that same call site also calls `linkRequired(...)` afterwards (as step 4 does), `linkRequired` has no other way to know the explicitly-linked context already satisfies the interface, and raises `IllegalStateException` for it. This is why the hand-written `GreetComponentContext` overrides `getProvidedInterfaces()` above even though it's never meant to be auto-discovered.
-
-### Overriding a built-in WASI Preview 2 context
-
-None of the `Wasi*Context` classes are `final`, and the built-ins are only ever *auto-linked* — never forced — so there are two ways to override one:
-
-1. **Per-linker, explicit**: call `linker.linkContext(...)` with your own instance (a subclass, or an unrelated implementation) using the *same* `name()` (e.g. `"wasi-cli"`) before calling `linkRequired(...)`. Since `linkRequired` only auto-links interfaces nothing has claimed yet, your explicitly-linked context wins and the built-in provider is never consulted.
-2. **Global, via lookup strategy**: register your own `ComponentContextLookup` (e.g. a `RegistryComponentContextLookup` populated with your preferred instances, or a custom implementation) as a `META-INF/services/io.github.stefanrichterhuber.wasmtimejavang.ComponentContextLookup` provider. `WasmtimeComponentLinker` resolves its dependency-lookup strategy the same SPI way, falling back to `ServiceLoaderComponentContextLookup` (which is what discovers the built-ins) only if nothing else is registered — so this replaces resolution for every linker in the process, not just one.
-
-Each `Wasi*Context` implements one small, generated **interface per WIT interface** it covers (e.g. `WasiSocketsContext implements NetworkContext, InstanceNetworkContext, TcpCreateSocketContext, TcpContext, UdpCreateSocketContext, UdpContext, IpNameLookupContext`), with every WIT function as a plain typed method — so overriding one function's behavior in a subclass needs nothing beyond overriding that one method:
-
-```java
-public class Ipv6TcpSocketsContext extends WasiSocketsContext {
- @Override
- public WitEnum tcpSocketAddressFamily(WasmtimeComponentInstance instance, WitResource self) {
- return new WitEnum("ipv6"); // the built-in implementation is IPv4-only
- }
-}
-```
-
-These generated interfaces are regular public types shipped in the jar, under `io.github.stefanrichterhuber.wasmtimejavang.wasip2.generated.wasicli` — see [Implementing your own component context](#implementing-your-own-component-context) above for how they're produced and how to generate your own from a custom WIT interface.
-
-### Version negotiation
-
-Every `WasmComponentContext` tracks a `SemanticVersion` (`withVersion`/`getVersion`), used to build the actual versioned interface name (e.g. `"wasi:cli/environment@0.2.6"`) each import is registered under — `getProvidedInterfaces()` itself stays version-independent (bare names), so lookup/auto-discovery doesn't need to know a component's exact required version up front. `getMiniumVersion()`/`getMaximumVersion()` (defaulted to accept anything) bound what `supportsVersion(...)` — and therefore `ComponentContextLookup.resolve(...)` — accepts; the built-in WASI contexts default to `0.2.6` (what `cargo build --target wasm32-wasip2` on current stable Rust actually emits) and accept `[0.0.1, 0.3.0]`, so they're ready for the eventual WASI 0.3 interfaces without code changes once toolchains catch up, but can be pinned with `.withVersion(...)` if a component needs something else within that range. `WasiHttpContext` is the one exception, defaulting to `0.2.8` and accepting `[0.2.0, 0.3.0]` — `wasi:http` isn't part of what the stable target emits automatically, so there's no single "current" version to match; `0.2.8` is simply what this project's own `wasi-http` WIT sources declare.
-
-## Architecture
-
-`wasmtimejavang` (the core artifact — see [Project Structure](#project-structure) above for the other modules) is structured into three distinct layers:
-
-1. **Java API**: A type-safe wrapper that represents Wasmtime concepts (Engine, Module, Store, Instance) in Java. It uses `jar-jni` for automatic native library loading. Log4j2 is used as logging framework. All called external functions (wasm32-wasip1) or components (wasm32-wasip2) are written in Java with a configurable dependency injection so that any function could be modified or rewritten by a java developer without knowledge about wasm.
-2. **JNI Layer (Rust)**: Built using the [jni-rs](https://crates.io/crates/jni) crate. It manages the lifecycle of native Wasmtime objects and facilitates data exchange between the JVM and Rust. This layer also redirects Rust `log` crate output to Java's Log4j2. "Classic" Java JNI was preferred over the newer "Foreign Function and Memory API", due to the native library is only planned to be used with Java so it could be tailored to its use. This allows more direct Rust - Java interactions like easily calling Java methods on objects or even create new Java objects using their constructor. A "Foreign Function an Memory API" approach would have resulted in a thinner native layer with far higher implementation effort on the Java side for all the type conversion, especially sacrificing the type and lifetime safety the current rust layer provides for the runtime.
-3. **Wasmtime Core**: The underlying [wasmtime](https://crates.io/crates/wasmtime) Rust crate.
-
-All modules are tested using JUnit. Test coverage is measured with Jacoco. The neccessary wasm modules for testing are build with rust (see each module's own `src/test/rust`, where present). Using `exec-maven-plugin` these wasm modules are build in the `generate-test-resources` phase.
-
-## Breaking changes
-
-* **Version 6.x.x**: Splitting the project into separate modules — three at first (`wasmtimejavang`, `wit-parser`, `wit-codegen-maven-plugin`), now further split so every WASI Preview 2 world ships as its own artifact depending on a `wasmtimejavang` core (`wasmtimejavang-wasi-{io,clocks,random,cli,filesystem,sockets,http}` — see [Project Structure](#project-structure)); Introduction of the wit-parser to automatically generate bindings for wasm32-wasip2; No changes for wasm32-wasip1 support
-* **Version 5.x.x**: Introduction of wasm32-wasip2 support; No changes for wasm32-wasip1 support
-* **Version 4.x.x**: Changed signature of Wasmfunction
-
-## Comparison to Previous Work
-
-This implementation aims to be more maintainable and feature-complete than earlier attempts:
-
-* [bluejekyll/wasmtime-java](https://github.com/bluejekyll/wasmtime-java)
-* [kawamuray/wasmtime-java](https://github.com/kawamuray/wasmtime-java)
+> **AI Disclaimer:** Parts of this project were vibe-coded or written with the help of an AI assistant (Claude); the remaining parts were written by hand.
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..6aa1c89
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,10 @@
+# Shuttle documentation
+
+| Document | Contents |
+| --- | --- |
+| [building.md](building.md) | Requirements; building the native core (`native/scripts/build.ps1`, `build.sh`, what `verify` checks); building the Maven modules and their profiles; CI workflows; known issues. |
+| [java-binding.md](java-binding.md) | The `shuttle-java` module: Wasmtime API (engine, module, store, linker, instance, memory, functions), value types, Component Model support, the Weave JNI layer for checkpointable guests, and the architecture (Java API / JNI layer in Rust / Wasmtime). |
+| [wit-codegen.md](wit-codegen.md) | `shuttle-wit-parser` and `shuttle-wit-maven-plugin`: generating Java component-context interfaces from WIT files. |
+| [../native/README.md](../native/README.md) | The native core prototype (`shuttle-core`, `shuttle-runner`, C guests): layout, build, test results, and which parts of the firmware ABI draft are implemented. |
+
+Back to the [project README](../README.md).
diff --git a/docs/building.md b/docs/building.md
new file mode 100644
index 0000000..710d10d
--- /dev/null
+++ b/docs/building.md
@@ -0,0 +1,146 @@
+# Building
+
+The repository has two independent builds:
+
+- **`native/`**: a Cargo workspace (the Shuttle native core). Built with the scripts in `native/scripts/`.
+- **The Maven reactor** (root `pom.xml`): `shuttle-java`, `shuttle-java-example`, `shuttle-wit-parser`, `shuttle-wit-maven-plugin`. Maven also drives the Rust builds of the two JNI libraries inside `shuttle-java` and `shuttle-wit-parser`.
+
+Neither build depends on the other.
+
+## Requirements
+
+| Tool | Needed for |
+| --- | --- |
+| Rust via [rustup](https://rustup.rs) (stable) | everything |
+| clang + wasm-ld | the C guests in `native/guests`. Windows: the [LLVM installer](https://github.com/llvm/llvm-project/releases). Linux/WSL2: `sudo apt install clang lld` |
+| JDK 21 and Maven | the Maven reactor (`maven.compiler.release` is 21) |
+| Rust target `wasm32-unknown-unknown` | the `shuttle-java` tests (the `valuetest` fixture is built for it): `rustup target add wasm32-unknown-unknown` |
+| [cross](https://github.com/cross-rs/cross) + Docker | only the Maven profiles that cross-compile (see below), and `build.sh --cross` |
+
+## Native core (`native/`)
+
+The scripts build the three C guests (`bios`, `os`, `child`) into freestanding wasm32 modules under `native/guests/out/`, run `cargo build --locked`, and then run `shuttle-runner verify`.
+
+Windows (PowerShell):
+
+```powershell
+.\native\scripts\build.ps1 # dev build + verify
+.\native\scripts\build.ps1 -Release # release build + verify
+.\native\scripts\build.ps1 -SkipVerify # build only
+```
+
+If `clang` is not on `PATH`, `build.ps1` adds `%ProgramFiles%\LLVM\bin`, because the LLVM installer does not always do this.
+
+Linux / WSL2:
+
+```sh
+./native/scripts/build.sh # dev build + verify
+./native/scripts/build.sh --release # release build + verify
+./native/scripts/build.sh --cross aarch64 # Linux arm64 via cross (build only, no verify)
+./native/scripts/build.sh --skip-verify # build only
+```
+
+By hand, inside `native/`:
+
+```sh
+sh guests/build.sh
+cargo build
+./target/debug/shuttle-runner run guests/out/bios.wasm guests/out/os.wasm guests/out/child.wasm
+./target/debug/shuttle-runner verify guests/out/bios.wasm guests/out/os.wasm guests/out/child.wasm
+```
+
+### What `verify` checks
+
+`run` boots BIOS → `exec` → OS → `co_create` → child and prints the log until power-off.
+
+`verify` does the following:
+
+1. Runs the machine once without interruption and records the log.
+2. For every tick boundary *N*, runs again, captures a snapshot at tick *N*, restores it into a brand-new machine, continues to power-off, and compares the log with step 1. All must be identical.
+3. A control run drops the host-side child table on restore. That run must diverge, which shows that the comparison actually detects lost state.
+
+The results of the last measured run are in [native/README.md](../native/README.md#results-2026-10-15-linux-x86_64-2-cores-dev-profile).
+
+## Maven reactor
+
+### Profiles
+
+The native library of `shuttle-java` (`wasmtime-binding`, built as `wasmtimebinding`) is compiled during the `compile` phase by a profile, and copied into the jar under `jp/akkiserver/shuttle/libs//`, where [jar-jni](https://github.com/questdb/jar-jni) finds it at runtime. Profiles defined in [`shuttle-java/pom.xml`](../shuttle-java/pom.xml):
+
+| Profile | Rust target | Built with | Jar folder |
+| --- | --- | --- | --- |
+| `build-linux-x86-64` (**active by default**) | `x86_64-unknown-linux-gnu` | `cross` (Docker) | `linux-x86-64` |
+| `build-linux-aarch64` | `aarch64-unknown-linux-gnu` | `cross` (Docker) | `linux-aarch64` |
+| `build-windows-x86_64` | `x86_64-pc-windows-msvc` | `cross` (Docker) | `win32-x86-64` |
+| `build-windows-aarch64` | `aarch64-pc-windows-msvc` | `cross` (Docker) | `win32-aarch64` |
+| `build-host-windows-x86_64` | `x86_64-pc-windows-msvc` | local `cargo` (no Docker) | `win32-x86-64` |
+
+There is no macOS profile yet, so `shuttle-java` cannot currently be packaged with a macOS library.
+
+Maven turns off an `activeByDefault` profile as soon as another profile of the same POM is selected with `-P`. So `-P build-host-windows-x86_64` builds only the Windows library and does not call `cross`.
+
+Profiles defined in the root [`pom.xml`](../pom.xml):
+
+| Profile | Effect |
+| --- | --- |
+| `release` (also activated by `-DperformRelease=true`) | Sets the Cargo profile to `release`, and adds GPG signing, source and javadoc jars, and the release plugin. GPG signing runs in the `verify` phase, so `install` with this profile needs a configured GPG key. |
+| `spotbugs` | Runs SpotBugs with FindSecBugs in the `test` phase (`spotbugs.failOnError` is `false`). |
+
+To get optimized Rust libraries without the release-publishing steps, set the two Cargo properties directly, as CI does:
+
+```sh
+mvn -Dcargo.profile=release -Dcargo.profile.folder=release install
+```
+
+### Commands
+
+Windows, local cargo only:
+
+```powershell
+# shuttle-java and the example (with shuttle-java's tests)
+mvn -P build-host-windows-x86_64 -pl shuttle-java,shuttle-java-example -am install
+
+# the whole reactor, skipping tests (see Known issues for why)
+mvn -P build-host-windows-x86_64 -DskipTests install
+```
+
+Linux, default profile (needs `cross` and a running Docker):
+
+```sh
+mvn install
+```
+
+Several platforms in one jar:
+
+```sh
+mvn -P build-linux-x86-64,build-linux-aarch64,build-windows-x86_64 install
+```
+
+Run the Weave example (after `shuttle-java` is installed in the local repository):
+
+```sh
+mvn -pl shuttle-java-example exec:java
+```
+
+### `shuttle-wit-parser`
+
+`shuttle-wit-parser` always builds its JNI library (`witparserbinding`) with a plain host `cargo build`, without `cross` or a profile, because it only runs on the build machine while the WIT plugin is generating sources.
+
+### Tests
+
+- `shuttle-java`: JUnit 5 tests (`WeaveTest`, `WasmtimeValueTest`, `WasmtimeComponentTest`, `WasmtimeMemoryTest`, ...). In `generate-test-resources`, `exec-maven-plugin` builds the Rust fixture `src/test/rust/valuetest` for `wasm32-unknown-unknown` into `target/rust-test/valuetest`. The other fixtures are inline WAT or inline Wasm bytes.
+- `shuttle-wit-parser` and `shuttle-wit-maven-plugin`: parse the WIT files under `src/test/resources/wit` (`greet.wit`, `wasi-cli`, `wasi-sockets`) and compile the generated sources.
+- Coverage is recorded with JaCoCo (`prepare-agent`, report in `prepare-package`).
+
+## CI
+
+| Workflow | Trigger | What it does |
+| --- | --- | --- |
+| [`native.yml`](../.github/workflows/native.yml) "Native (Shuttle)" | push to `main` or PR touching `native/**` or the workflow; manual dispatch | Builds the C guests once on Ubuntu and uploads them as an artifact. Then, on Linux, Windows and macOS (x86_64 and arm64 each, six jobs): `cargo build --locked --release` and `shuttle-runner verify`. |
+| [`maven.yml`](../.github/workflows/maven.yml) "Java CI with Maven" | push or PR to `main` | Ubuntu, JDK 21 (Temurin): installs `cross`, then runs `mvn -P build-linux-x86-64,build-linux-aarch64,build-windows-x86_64 -Dcargo.profile=release -Dcargo.profile.folder=release -B package`. |
+
+## Known issues
+
+- **`shuttle-wit-parser` tests fail on Windows.** Its `pom.xml` only copies the Linux x86_64 library (`*.so` into `jp/akkiserver/shuttle/wit/parser/libs/linux-x86-64`). On Windows the DLL is built but never copied into the jar folder, so loading fails with `cannot find .../win32-x86-64/witparserbinding.dll`. The `shuttle-wit-maven-plugin` tests use the parser too. On Windows, skip tests for these modules (`-DskipTests`) or build only `shuttle-java` (`-pl shuttle-java -am`).
+- **"Java CI with Maven" is failing**, also on `main`. The cause has not been investigated yet. Possible factors: the workflow installs `wasm32-wasip1`/`wasm32-wasip2` targets (left over from the removed WASI modules) but not `wasm32-unknown-unknown`, which the `valuetest` fixture needs, and it cross-compiles `x86_64-pc-windows-msvc` with `cross`.
+- **No macOS Maven profile.** `native/` builds on macOS in CI, but `shuttle-java` has no profile that builds or packages a macOS library.
diff --git a/docs/java-binding.md b/docs/java-binding.md
new file mode 100644
index 0000000..7b244ad
--- /dev/null
+++ b/docs/java-binding.md
@@ -0,0 +1,292 @@
+# Java binding (`shuttle-java`)
+
+`shuttle-java` is the Java side of Shuttle that a host mod calls. It has two parts:
+
+1. **Wasmtime bindings** (inherited from the upstream wasmtime-ng project): engine, modules, stores, linkers, instances, linear memory, host functions, and the Component Model.
+2. **The Weave layer**: load a core Wasm module in *woven* (checkpointable) form, run it in slices, snapshot it into a byte array, and restore it later, possibly in another JVM.
+
+All classes are in the package `jp.akkiserver.shuttle` (WIT value wrappers are in `jp.akkiserver.shuttle.component`). The native code is one Rust library, `wasmtimebinding` (source: [`shuttle-java/src/main/rust/wasmtime-binding`](../shuttle-java/src/main/rust/wasmtime-binding)). It is packaged inside the jar and loaded automatically.
+
+How to build the jar, including which native platforms end up inside it, is described in [building.md](building.md).
+
+## Dependency
+
+The project is not published to a Maven repository yet. Install it locally (see [building.md](building.md)) and depend on:
+
+```xml
+
+ jp.akkiserver
+ shuttle-java
+ 6.0.1-SNAPSHOT
+
+```
+
+Runtime dependencies: `org.questdb:jar-jni` (loads the native library) and `log4j-api`. Native log output from Rust is forwarded to Log4j2. A Log4j2 implementation is not included; inside Minecraft the game's own Log4j2 is used.
+
+## Wasmtime API
+
+| Class | Role |
+| --- | --- |
+| `WasmtimeEngine` | Wasmtime engine. Shared and reused across modules. `precompile(...)` produces a precompiled (`.cwasm`) image. |
+| `WasmtimeModule` | Compiled core module. Constructors take a WAT `String`, `byte[]`, `ByteBuffer` or `InputStream` (binary or text). `fromPrecompiled(...)` loads a `.cwasm` image. |
+| `WasmtimeStore` | Wasmtime store. Holds a user context `Map` (`getContext()`), reachable from host functions through the instance. |
+| `WasmtimeLinker` | Defines imports: `linkFunction(...)`, `linkContext(WasmContext)`, `linkMemory(...)`. (`importFunction`, `link` and `defineSharedMemory` are deprecated aliases.) |
+| `WasmtimeInstance` | Instantiated module: `invoke(name, args...)`, `start()` (calls `_start`), `getFunction(name)`, `getMemory(name)`. |
+| `WasmtimeMemory` | Linear memory view over a `java.nio.ByteBuffer` (implemented by `WasmtimeLocalMemory` and `WasmtimeSharedMemory`). |
+| `WasmtimeFunction` | Functional interface `Object[] call(WasmtimeInstance instance, Object... args)` used for host functions and for exported function references. |
+| `WasmContext` | A reusable bundle of import functions (`ImportFunction`) and shared memories (`Importmemory`) for `linkContext`. |
+
+All native-backed classes implement `AutoCloseable`. Use try-with-resources.
+
+### Host functions
+
+A host function is a Java method that a Wasm module imports:
+
+```java
+import java.util.List;
+import jp.akkiserver.shuttle.*;
+
+String wat = """
+ (module
+ (func $hello (import "env" "hello"))
+ (func (export "run") (call $hello))
+ )
+ """;
+
+try (
+ WasmtimeEngine engine = new WasmtimeEngine();
+ WasmtimeModule module = new WasmtimeModule(engine, wat);
+ WasmtimeStore store = new WasmtimeStore(engine);
+ WasmtimeLinker linker = new WasmtimeLinker(engine, store)
+) {
+ store.getContext().put("greeting", "Hello world");
+
+ // env.hello: no parameters, no results
+ linker.linkFunction("env", "hello", List.of(), List.of(), (instance, params) -> {
+ System.out.println("Java side: " + instance.getContext().get("greeting"));
+ return new Object[] {};
+ });
+
+ try (WasmtimeInstance instance = new WasmtimeInstance(store, module, linker)) {
+ instance.invoke("run"); // prints "Java side: Hello world"
+ }
+}
+```
+
+Parameter and result types are given as `ValType` values: `I32`, `I64`, `F32`, `F64`, `V128`, `Ref` (externref).
+
+### Supported value types
+
+Conversions work in both directions unless noted otherwise.
+
+| Wasm type | Java type | Notes |
+| :--- | :--- | :--- |
+| `i32` | `java.lang.Integer` | Numbers are always boxed. |
+| `i64` | `java.lang.Long` | |
+| `f32` | `java.lang.Float` | |
+| `f64` | `java.lang.Double` | |
+| `v128` | `jp.akkiserver.shuttle.V128` | Wrapper around `byte[16]`, with conversions from and to `byte[]`, `short[]`, `int[]`, `long[]` and `BigInteger`. |
+| `funcref` | `jp.akkiserver.shuttle.WasmtimeFunction` | Callable function reference. Only Wasm → Java (exported function references) is supported. |
+| `externref` | any Java object | Any Java object can be passed through Wasm as an externref (`ValType.Ref`). |
+
+### Memory
+
+`WasmtimeMemory` maps linear memory directly to a `java.nio.ByteBuffer`:
+
+```java
+try (WasmtimeInstance instance = new WasmtimeInstance(store, module, linker)) {
+ WasmtimeMemory mem = instance.getMemory("memory");
+
+ byte[] data = mem.read(0x1000, 4); // 4 bytes from offset 0x1000
+ mem.write(0x1003, (byte) 5); // one byte at 0x1003
+
+ // Normally the guest's own allocator would hand out these addresses.
+ mem.writeCString(0x1000, "Hello", StandardCharsets.UTF_8); // NUL-terminated
+ mem.readCString(0x1000, StandardCharsets.UTF_8); // "Hello"
+
+ mem.writeString(0x1000, "World", StandardCharsets.UTF_8); // no terminator
+ mem.readString(0x1000, 5, StandardCharsets.UTF_8); // "World"
+
+ mem.writeLong(0x1000, Long.MAX_VALUE); mem.readLong(0x1000);
+ mem.writeInt(0x1000, Integer.MAX_VALUE); mem.readInt(0x1000);
+ mem.writeShort(0x1000, Short.MAX_VALUE); mem.readShort(0x1000);
+ mem.write(0x1000, Byte.MAX_VALUE); mem.readByte(0x1000);
+
+ mem.grow(1); // add one 64 KiB page
+}
+```
+
+`createView(address, length)` returns a `WasmtimeMemory` over a sub-range. `WasmtimeSharedMemory(engine, initialPages, maxPages)` creates a shared memory that can be imported with `linker.linkMemory(module, name, memory)`.
+
+### WASI
+
+`shuttle-java` currently contains **no WASI implementation**. Upstream implemented WASI Preview 1 (`WasiPI1Context`, `WasiThreadContext`) and WASI Preview 2 (`wasmtimejavang-wasi-*` modules) in Java. All of these were removed in commit `44c4363` ("remove wasi components"). Shuttle guests talk to the host through the firmware ABI instead of WASI. A module that imports WASI functions only links if the host provides those imports itself, for example with `linkFunction`.
+
+## Component Model
+
+The Component Model plumbing is still present, without any built-in interfaces:
+
+| Class | Role |
+| --- | --- |
+| `WasmtimeComponent` | Compiled component (WAT or binary from `String` bytes, `byte[]`, `ByteBuffer`, `InputStream`). `getImportInterfaces()` / `getExportInterfaces()` read the named interfaces straight from the compiled component, without instantiating it. |
+| `WasmtimeComponentLinker` | `linkContext(WasmComponentContext)` links one context (after its declared dependencies). `linkRequired(component)` links a provider for every import interface not already covered, found through a `ComponentContextLookup`. It throws `IllegalStateException` if an interface stays unresolved. |
+| `WasmtimeComponentInstance` | Instantiated component. `invoke(interfaceName, funcName, args...)` calls an export; use `""` as the interface name for root-level exports. |
+| `WasmComponentContext` | A host-side implementation of one or more component interfaces. |
+
+### Component values
+
+| WIT type | Java type |
+| :--- | :--- |
+| `bool` | `Boolean` |
+| `s8`, `u8`, `s16`, `u16`, `s32`, `u32` | `Integer` |
+| `s64`, `u64` | `Long` |
+| `f32` / `f64` | `Float` / `Double` |
+| `char` | `Character` |
+| `string` | `String` |
+| `list` | `byte[]` |
+| `list` | `List