diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 5d579a655..2b5c3acc8 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -12,6 +12,7 @@ But first, read this page (including the small print at the end).
+ [Coding Guidelines](#coding-guidelines)
+ [Continuous Integration](#continuous-integration)
+ [Tests and documentation are not optional](#tests-and-documentation-are-not-optional)
+ + [Writing docs](#writing-docs)
* [Reporting an issue](#reporting-an-issue)
* [AI Tool Use Policy](#ai-tool-use-policy)
* [Legal](#legal)
@@ -159,6 +160,17 @@ To automatically apply and approve e new version of the "Golden samples" used by
APPROVAL_TESTS_USE_REPORTER=AutoApproveReporter
```
+### Writing docs
+
+The website lives in `docs/` (see [its README](docs/README.md) for the build). Every page under `docs/docs/` follows these rules:
+
+- Describe how to use a feature, not how it is implemented. Internals belong in Javadoc or in the pull request description.
+- No release-time or migration statements. Those go in a blog post; breaking changes relative to Chicory go in the migration guide.
+- One code sample per option. Show a builder once per page.
+- Body headings start at `##`; the frontmatter `title` is the page heading. Open every page with one plain sentence saying what it covers, which becomes its description in `llms.txt`.
+- Names: Wasm, interpreter, runtime compiler, build-time compiler. Version placeholder: `${endive.version}`.
+- Every page is executed by `npm test` in `docs/`: keep the hidden `//DEPS` and `docs.FileOps.writeResult(...)` blocks, and update `docs/tests/approvals/` when a page moves.
+
## Reporting an issue
This project uses GitHub issues to manage the issues. Open an issue directly in GitHub.
diff --git a/docs/README.md b/docs/README.md
index 9d6cc86a6..35f5aff81 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -24,3 +24,5 @@ $ npm run build
This command generates static content into the `build` directory and can be served using any static contents hosting service.
+
+The rules for writing pages are in [CONTRIBUTING.md](../CONTRIBUTING.md#writing-docs).
diff --git a/docs/blog/2025-02-20-chicory-1.1.0.md b/docs/blog/2025-02-20-chicory-1.1.0.md
index b06301415..59f6026af 100644
--- a/docs/blog/2025-02-20-chicory-1.1.0.md
+++ b/docs/blog/2025-02-20-chicory-1.1.0.md
@@ -33,5 +33,5 @@ With this release, weβre introducing a **βWho Uses Chicory?β** section in
Try out **Chicory 1.1.0** today, and let us know what you think!
-[simd]: /docs/advanced/simd
+[simd]: /docs/execution
[memory]: /docs/advanced/memory-customization
diff --git a/docs/docs/advanced/logging.md b/docs/docs/advanced/logging.md
index 50f10caf1..47913743e 100644
--- a/docs/docs/advanced/logging.md
+++ b/docs/docs/advanced/logging.md
@@ -3,8 +3,6 @@ sidebar_position: 4
sidebar_label: Logging
title: Logging
---
-# Logging
-
For maximum compatibility and to avoid external dependencies we use, by default, the JDK Platform Logging ([JEP 264](https://openjdk.org/jeps/264)).
The Platform Logging falls back to the [`java.util.logging` API](https://docs.oracle.com/en/java/javase/21/core/java-logging-overview.html) by default.
diff --git a/docs/docs/advanced/memory-customization.md b/docs/docs/advanced/memory-customization.md
index 3f6db6850..bc6fe88a1 100644
--- a/docs/docs/advanced/memory-customization.md
+++ b/docs/docs/advanced/memory-customization.md
@@ -1,7 +1,7 @@
---
sidebar_position: 5
-sidebar_label: Memory
-title: Advanced Wasm Memory Customization
+sidebar_label: Memory Customization
+title: Memory Customization
---
-Different Wasm workloads will be using the memory in very different ways.
-
-Make sure you don't do changes without proper benchmarking and analysis.
-
-It's possible to provide a custom implementation of the entire Memory used by the Wasm module:
+Endive ships two implementations of linear memory, and `withMemoryFactory` selects which one backs an instance:
```java
-var instance = Instance.builder(module).withMemoryFactory(limits -> {
- return new ByteArrayMemory(limits);
- }).build();
+var instance = Instance.builder(module).
+ withMemoryFactory(ByteArrayMemory::new).
+ build();
```
-:::note
-Endive provides two built-in Memory implementations:
-- `ByteArrayMemory`: An optimized memory implementation, recommended for recent OpenJDK systems
-- `ByteBufferMemory`: Recommended for different Java runtimes (in particular, on Android VMs)
-:::
+## Choosing an implementation
+
+| Implementation | Plain loads and stores | Atomic instructions | Works on |
+|---|---|---|---|
+| `ByteBufferMemory` (default) | Good | Always take a lock | Every JDK and Android |
+| `ByteArrayMemory` | Fastest on every JDK | Lock-free up to JDK 21, locked from JDK 23 | Every JDK |
+
+Atomic instructions only matter for modules built with the threads proposal that share their memory between threads.
+For everything else `ByteArrayMemory` is the faster choice on a standard JVM, and `ByteBufferMemory` is the safe default where that cannot be assumed, such as Android.
+From JDK 23 the JDK no longer provides lock-free multi-byte atomics on byte arrays, so a shared memory performs the same with either implementation there.
+
+Workloads use memory in very different ways, so benchmark before changing the default.
+
+[Redline](../experimental/redline.md#native-memory) keeps linear memory off-heap with lock-free atomics; that memory comes with the Redline runners and is not selectable here.
+
+Any class implementing `Memory` can be passed to `withMemoryFactory`, for example an off-heap implementation of your own.
+The factory receives the module's declared limits, or the ones set with [`withMemoryLimits`](../core/memory.md#limiting-memory-growth).
diff --git a/docs/docs/advanced/tools.md b/docs/docs/advanced/tools.md
index d7f1e4a4b..351c26ccb 100644
--- a/docs/docs/advanced/tools.md
+++ b/docs/docs/advanced/tools.md
@@ -1,34 +1,31 @@
---
sidebar_position: 3
-sidebar_label: WABT
-title: WABT
+sidebar_label: Tools
+title: Tools
---
-# Tools
+Endive publishes a few WebAssembly tools as Maven artifacts, compiled to Wasm and run with Endive itself.
## WebAssembly Binary Toolkit
-Since we use them in the build of the project, we publish a few [wabt](https://github.com/WebAssembly/wabt) tools compiled at build time with Endive.
-Adding support for more tools is easy and we welcome contributions in this direction.
-The relevant module can be added to the build with:
+A few [wabt](https://github.com/WebAssembly/wabt) tools, the ones Endive's own build uses:
```xml
run.endive
wabt
- latest-release
+ ${endive.version}
```
## Wasm Tools
-As we need it to catch up with the latest development of the Wasm spec we publish a few [wasm-tools](https://github.com/bytecodealliance/wasm-tools) compiled at build time with Endive.
-The relevant module can be added to the build with:
+A few [wasm-tools](https://github.com/bytecodealliance/wasm-tools) binaries, which track the latest Wasm proposals:
```xml
run.endive
wasm-tools
- latest-release
+ ${endive.version}
```
@@ -49,8 +46,7 @@ System.setOut(new PrintStream(
## wat2wasm
-In Endive, we don't have a Wasm text format parser just yet.
-To overcome this limitation you can use `wat2wasm`, for example:
+Endive does not parse the Wasm text format. Use `wat2wasm` to convert text to binary:
```java
import run.endive.wabt.Wat2Wasm;
diff --git a/docs/docs/ai-friendly-docs.md b/docs/docs/ai-friendly-docs.md
index 358ebec33..c4a820b8b 100644
--- a/docs/docs/ai-friendly-docs.md
+++ b/docs/docs/ai-friendly-docs.md
@@ -4,8 +4,6 @@ sidebar_label: AI-Friendly Docs
title: AI-Friendly Documentation
---
-# AI-Friendly Documentation
-
Endive documentation is designed to be easily consumed by LLMs, AI agents, and developer tools. All machine-readable files are auto-generated at build time from the same source as the human-facing site, using the [llms.txt specification](https://llmstxt.org/).
## Available Endpoints
@@ -29,8 +27,7 @@ Every documentation page also has a **raw Markdown** version available by append
Fetch `llms-full.txt` and include it in your prompt context:
```bash
-curl -s https://endive.run/llms-full.txt | wc -c
-# ~86 KB β fits easily in any modern LLM context window
+curl -s https://endive.run/llms-full.txt
```
### Let an AI agent discover relevant pages
diff --git a/docs/docs/annotations/_category_.yml b/docs/docs/annotations/_category_.yml
deleted file mode 100644
index 3f46b5dc7..000000000
--- a/docs/docs/annotations/_category_.yml
+++ /dev/null
@@ -1,2 +0,0 @@
-label: Annotations
-position: 35
diff --git a/docs/docs/annotations/index.md b/docs/docs/core/annotations.md
similarity index 75%
rename from docs/docs/annotations/index.md
rename to docs/docs/core/annotations.md
index 5bdb4c079..9f486b3da 100644
--- a/docs/docs/annotations/index.md
+++ b/docs/docs/core/annotations.md
@@ -1,13 +1,43 @@
---
-sidebar_position: 100
+sidebar_position: 4
sidebar_label: Annotations
title: Annotations
---
-## Host Modules
+The annotation processor generates host functions from annotated Java methods, and typed Java bindings from a Wasm module, so you do not write the `HostFunction` boilerplate by hand.
-Instead of writing host functions by hand, you can write a class containing annotated methods
-and let the Endive annotation processor generate the host functions for you. This is especially
-useful when you have many host functions.
+## Setup
+
+Add the annotations dependency:
+
+```xml
+
+ run.endive
+ annotations
+ ${endive.version}
+
+```
+
+and register `annotations-processor` as an annotation processor of the Java compiler, with Maven:
+
+```xml
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ run.endive
+ annotations-processor
+ ${endive.version}
+
+
+
+
+```
+
+## Host modules
+
+Annotate the methods of a class and the processor generates one host function per method:
diff --git a/docs/docs/core/execution-modes.md b/docs/docs/core/execution-modes.md
deleted file mode 100644
index 958efa352..000000000
--- a/docs/docs/core/execution-modes.md
+++ /dev/null
@@ -1,29 +0,0 @@
----
-sidebar_position: 4
-sidebar_label: Execution modes
-title: Execution modes
----
-
-## Overview
-
-| Mode | Performance | Dynamic Module Loading | Requirements | Output Format | Ideal Use Case |
-|---|---|---|---|---|---|
-| **Interpreter** | π’ Slow | β
Supported | None | None - fully interpreted | Default mode; highly portable; suitable for development and environments requiring dynamic loading. |
-| **Runtime Compilation** | π Fast | β
Supported | Requires reflection and ASM dependency | In-memory Java Bytecode | Enhanced performance; suitable when dynamic loading is needed and the usage of reflection is fine. |
-| **Build time Compilation** | π Fast | β Not Supported | Build-time tools (e.g., Maven or Gradle plugins) | Plain Java Bytecode | Optimal performance; no dynamic loading; ideal for production with static modules. |
-
-## Summary
-
-- **Interpreter**: Executes WebAssembly (Wasm) modules directly without prior compilation. It's the default mode in Endive, offering maximum portability and simplicity. However, it has slower execution speed compared to compiled modes.
-
-- **Runtime Compilation**: Compiles Wasm modules to Java bytecode at runtime for fast execution. This mode requires one additional dependency on [ASM](https://asm.ow2.io/), it uses reflection, and it loads bytecode dynamically. It fully supports loading new Wasm modules on-the-fly, but it might not be supported on some platforms (such as Android, or GraalVM's native-image).
-
-- **Build time Compilation**: Compiles Wasm modules to Java bytecode during the build process using tools like Maven or Gradle plugins. This mode offers the best performance and eliminates the need for dynamic loading and additional dependencies, making it ideal for production environments with static modules.
-
-
diff --git a/docs/docs/core/gc-types.md b/docs/docs/core/gc-types.md
new file mode 100644
index 000000000..ad0fa08cf
--- /dev/null
+++ b/docs/docs/core/gc-types.md
@@ -0,0 +1,90 @@
+---
+sidebar_position: 5
+sidebar_label: GC Types
+title: GC Types
+---
+
+Modules built with the WebAssembly garbage collection proposal exchange structs, arrays and `externref` values with the host as ordinary Java objects, which the JVM garbage collector manages like any other.
+
+Download a module that defines a `point` struct:
+
+```bash
+curl https://raw.githubusercontent.com/bytecodealliance/endive/main/wasm-corpus/src/main/resources/compiled/gc.wat.wasm > gc.wasm
+```
+
+
+
+## Calling exports that use references
+
+Use `applyWithRefs` instead of `apply` for an export whose signature contains a reference type; `apply` throws on those.
+The first array has one slot per parameter and carries the numeric arguments; the second carries the reference arguments, in order:
+
+```java
+import run.endive.runtime.Instance;
+import run.endive.runtime.WasmStruct;
+import run.endive.wasm.Parser;
+import java.io.File;
+
+var instance = Instance.builder(Parser.parse(new File("./gc.wasm"))).build();
+
+// (param i32 i32) (result (ref $point))
+var point = (WasmStruct) instance.export("new_point").applyWithRefs(new long[] {3, 4}, null).refResult(0);
+
+// (param (ref null $point)) (result i32)
+var sum = instance.export("sum").applyWithRefs(new long[] {0}, new Object[] {point}).longResult(0);
+assert(7L == sum);
+```
+
+The returned `CallResult` exposes numeric results through `longResult(i)` and reference results through `refResult(i)`.
+
+## Reading and building structs and arrays
+
+`WasmStruct` exposes its fields by index: `field(i)` for numeric fields and `fieldRef(i)` for reference fields.
+`WasmArray` does the same with `get(i)` and `getRef(i)`.
+To create one on the host, use the builder and add the fields in declaration order:
+
+```java
+assert(3L == point.field(0));
+
+var origin = WasmStruct.builder().
+ typeIdx(point.typeIdx()).
+ addField(0).
+ addField(0).
+ build();
+assert(0L == instance.export("sum").applyWithRefs(new long[] {0}, new Object[] {origin}).longResult(0));
+```
+
+
+
+## Host functions receiving references
+
+A host function whose signature contains reference types implements `applyWithRefs` on `WasmFunctionHandle`, and is then wrapped in a `HostFunction` like any other:
+
+```java
+import run.endive.runtime.CallResult;
+import run.endive.runtime.WasmFunctionHandle;
+
+var handle = new WasmFunctionHandle() {
+ @Override
+ public long[] apply(Instance instance, long... args) {
+ throw new UnsupportedOperationException("called with references, use applyWithRefs");
+ }
+
+ @Override
+ public CallResult applyWithRefs(Instance instance, long[] args, Object[] refArgs) {
+ var p = (WasmStruct) refArgs[0];
+ return CallResult.of(new long[] {p.field(0) + p.field(1)}, null);
+ }
+};
+```
diff --git a/docs/docs/core/host-functions.md b/docs/docs/core/host-functions.md
index 8a905569b..86b469b1b 100644
--- a/docs/docs/core/host-functions.md
+++ b/docs/docs/core/host-functions.md
@@ -3,47 +3,19 @@ sidebar_position: 1
sidebar_label: Host Functions
title: Host Functions
---
-# Host and guests
+A host function is a Java function that a Wasm module imports and calls, and it is the way a module reaches anything outside its sandbox.
:::warning[Security Consideration]
-Host functions cross the sandbox boundary. Validate all arguments received from Wasm code β especially memory offsets and lengths β before accessing host resources. Never trust guest-provided pointers without bounds checking. See [Security Best Practices](/docs/security/best-practices).
+Host functions cross the sandbox boundary: validate every argument that comes from Wasm, especially memory offsets and lengths, before touching host resources. See [Security Best Practices](/docs/security/best-practices).
:::
-In Wasm, the instance of a module is generally regarded as the **guest**,
-and the surrounding runtime environment is called the **host**.
+In Wasm terms the module instance is the **guest** and the surrounding runtime, your Java application, is the **host**.
+A module can **export** functions for the host to call, and **import** functions that the host must provide when the module is instantiated.
+Without imports a module is pure compute: it cannot perform I/O or interact with other modules.
-For example, an **application** using Endive as a **library** would be the **host**
-to a Wasm module **guest** you have instantiated.
+## Writing a host function
-Wasm modules may **export** functions, so that they can be externally invoked.
-But Wasm modules may also **import** functions.
-These imports have to be resolved at the time when a module is instantiated;
-in other words, when a module is instantiated, the runtime has to provide
-references for all the imports that a module declares.
-
-The import/export mechanism is the way through which Wasm interacts
-with the outside world: without imports, a Wasm module is "pure compute",
-that is, it cannot perform any kind of I/O, nor can it interact with other
-modules.
-
-## Host Functions
-
-One way to fulfill imports is providing a **host function** written in Java.
-No matter what source language that your Wasm module was written in,
-it will be able to call this Java function when needed.
-
-It is called a **host** function because it is executed in the environment of the
-**host** (in this case, a JVM). As opposed to any other Wasm function,
-a **host function** is _unrestricted_ and it may interact with the surrounding
-environment in any arbitrary way. This lets you effectively escape the sandbox.
-As a consequence, host functions are a security boundary and
-need to be implemented carefully if the Wasm code is not trusted.
-
-You can think of host functions as similar to OS system calls or the standard library in your favorite programming language.
-The key difference is that, instead of relying on a default implementation, you use Java to define their behavior
-and determine what they do.
-
-Let's see it with another example. Download the following Wasm binary:
+Download the following module, which imports a `console.log` function:
```bash
curl https://raw.githubusercontent.com/bytecodealliance/endive/main/wasm-corpus/src/main/resources/compiled/host-function.wat.wasm > logger.wasm
@@ -58,9 +30,7 @@ docs.FileOps.copyFromWasmCorpus("host-function.wat.wasm", "logger.wasm");
```
-->
-This module expects us to fulfil an import with the name `console.log`.
-As the name implies, this function allows a caller to log a message to standard output.
-We could write it as the host function:
+The module expects an import named `console.log` that prints a message, written as a host function like this:
+:::tip
+A module like this one is a Rust library built with `rustc --target=wasm32-unknown-unknown --crate-type=cdylib`. Use `--target=wasm32-wasip1` for programs that need [WASI](../wasi/index.md).
+:::
+
Build and instantiate this module:
```java
@@ -68,6 +69,25 @@ dealloc.apply(ptr, len);
assert(3L == result); // 3 vowels in Hello, World!
```
+## Limiting memory growth
+
+A module declares the initial and maximum size of its memory in 64 KiB pages, and the maximum defaults to 4 GiB when it declares none.
+To cap how far an untrusted module can grow, override the limits of its memory when building the instance:
+
+```java
+import run.endive.wasm.types.MemoryLimits;
+
+// keep the declared 17 initial pages, allow growth up to 32 pages (2 MiB)
+var limited = Instance.builder(Parser.parse(new File("./count_vowels.wasm"))).
+ withMemoryLimits(new MemoryLimits(17, 32)).
+ build();
+```
+
+Past the maximum, `memory.grow` fails inside the module and the instance keeps running.
+The override applies to the first memory the module defines; an imported memory keeps the limits it was created with.
+Memory limits bound linear memory only: structs and arrays allocated by a [GC](gc-types.md) module live on the JVM heap and are bounded only by its size.
+To change which implementation backs the memory, see [Memory Customization](../advanced/memory-customization.md).
+
-
-```java
-import run.endive.wasm.Parser;
-import run.endive.runtime.Instance;
-
-var instance = Instance.builder(Parser.parse(new File("count_vowels.rs.wasm"))).build();
-
-var alloc = instance.export("alloc");
-var dealloc = instance.export("dealloc");
-var countVowels = instance.export("count_vowels");
-
-var memory = instance.memory();
-var message = "Hello, World!";
-byte[] bytes = message.getBytes();
-int len = bytes.length;
-int ptr = (int) alloc.apply(len)[0];
-
-memory.write(ptr, bytes);
-
-var result = countVowels.apply(ptr, len)[0];
-System.out.println(result);
-```
diff --git a/docs/docs/execution/build-time-compiler.md b/docs/docs/execution/build-time-compiler.md
index 71f3fe790..ff4c3d355 100644
--- a/docs/docs/execution/build-time-compiler.md
+++ b/docs/docs/execution/build-time-compiler.md
@@ -1,66 +1,26 @@
---
sidebar_position: 2
-sidebar_label: Build time Compilation
-title: Build Time Compilation
+sidebar_label: Build-time Compiler
+title: Build-time Compiler
---
-## Overview
+
+The build-time compiler translates Wasm functions to JVM bytecode during your build and stores them as `.class` files that you package in your application.
:::warning[Security Consideration]
-The compiler translates Wasm to JVM bytecode without post-compilation verification. Only compile Wasm modules you trust. See [Security Model](/docs/security/overview).
+The compiler generates JVM bytecode without a separate verification pass. Only compile modules you trust. See [Security Model](/docs/security/overview).
:::
-The build time compiler backend is a drop-in replacement for the interpreter, and it passes 100% of the same
-spec tests that the interpreter already supports.
-
-This compiler translates the WASM instructions to Java bytecode and stores them as `.class` files
-that you package in your application. The resulting code is usually expected to evaluate (much) faster and
-consume less memory than if it was interpreted.
-
-The build time compiler has several advantages over the [Runtime Compiler](runtime-compiler.md) such as:
-
-- improved instance initialization time: the translation occurs at build time
-- no reflection needed: easier to use with `native-image`
-- fewer runtime dependencies: ASM is only needed at build time
-- distribute Wasm modules as self-contained jars: making it a convenient way to distribute software that was not originally meant to run on the Java platform
+Compared to the [runtime compiler](runtime-compiler.md):
-You can use the compiler at build-time via Maven plug-in, Gradle plug-in, or plain CLI.
+- nothing is compiled at instantiation
+- no reflection and no ASM at runtime, so it works with `native-image`
+- the module ships as a self-contained jar that needs only `run.endive:runtime` at runtime
-### Interpreter Fall Back
-
-The WASM to bytecode compiler translates each WASM function into JVM method. Occasionally you will find WASM module where functions are bigger than the maximum method size allowed by the JVM. In these rare cases, we fall back to executing these large functions in the interpreter.
-
-Since interpreted functions have worse performance, we want to make sure you are aware this is happening so the build time compiler will FAIL if it finds any functions that are too large. The build tool will produce a message that contains text like:
-
-```text
-WASM function size exceeds the Java method size limits and cannot be compiled to Java bytecode. It can only be run in the interpreter. Either reduce the size of the function or enable the interpreter fallback mode: WASM function index: 3938
-```
+Modules that use SIMD (`v128`) instructions cannot be compiled and must run in the interpreter.
-If this happens you can configure your build tool, to just issue warning messages, or to be silent. Another way to silence the message is to configure the build tool with an explicit list of functions that should be interpreted. Typically, you obtain the list of the functions by running the compiler once with `interpreterFallback` set to `WARN`
+## Maven
-### Method Names
-
-By default the compiler names each generated method `func_0`, `func_1`, etc. Set `methodPrefixer` to
-name them after the module's name section instead, so that thread dumps, profiler output and stack
-traces identify functions by name:
-
-```xml
-
- src/main/resources/add.wasm
- org.acme.wasm.Add
- run.endive.compiler.NameSectionMethodPrefixer
-
-```
-
-The names are resolved at build time and baked into the generated classes; the name section itself is
-not shipped with them.
-
-The parameter accepts any `run.endive.compiler.MethodPrefixer` with a public no-argument constructor,
-declared as a dependency of the plugin. See [Method Names](runtime-compiler.md#method-names) for the
-naming contract.
-
-## Using Maven
-
-Example configuration of the Maven plug-in:
+Configure the plugin:
```xml
@@ -87,7 +47,7 @@ Example configuration of the Maven plug-in:
```
-In the codebase you can use the generated module by configuring appropriately the `MachineFactory`:
+Then use the generated class as the machine factory:
diff --git a/docs/docs/execution/runtime-compiler.md b/docs/docs/execution/runtime-compiler.md
index a27b8eef8..3b34bdb05 100644
--- a/docs/docs/execution/runtime-compiler.md
+++ b/docs/docs/execution/runtime-compiler.md
@@ -1,34 +1,22 @@
---
sidebar_position: 1
-sidebar_label: Runtime Compilation
-title: Runtime Compilation
+sidebar_label: Runtime Compiler
+title: Runtime Compiler
---
-## Overview
-:::warning[Security Consideration]
-The compiler translates Wasm to JVM bytecode without post-compilation verification. When compiling untrusted modules, prefer the interpreter for higher assurance, or run compiled code in an isolated classloader. See [Security Model](/docs/security/overview).
-:::
+The runtime compiler translates Wasm functions to JVM bytecode in memory when the instance is created, as a drop-in replacement for the interpreter.
+Compiled code runs much faster and uses less memory than interpreted code, at the cost of a small delay at instantiation.
+Use the [build-time compiler](./build-time-compiler.md) to move that work into your build.
-:::info[Resource Limits]
-Compiling very large Wasm modules can consume significant memory and CPU. Consider setting JVM heap limits and compilation timeouts when processing untrusted input.
+:::warning[Security Consideration]
+The compiler generates JVM bytecode without a separate verification pass. Prefer the interpreter for untrusted modules, and bound heap and time when compiling untrusted input. See [Security Model](/docs/security/overview).
:::
-The runtime compiler backend is a drop-in replacement for the interpreter, and it passes 100% of the same
-spec tests that the interpreter already supports.
-
-This runtime compiler translates the WASM instructions to Java bytecode on-the-fly in-memory.
-The resulting code is usually expected to evaluate (much) faster and consume less memory than if it
-was interpreted.
+Modules that use SIMD (`v128`) instructions cannot be compiled and must run in the interpreter.
-At the current time, the compiler will eagerly compile all WASM instructions to Java bytecode. You end up
-paying a small performance penalty at Instance initialization, but the execution speedup
-is usually worth it. Use [Build Time Compilation](./build-time-compiler.md) if you want to avoid the penalty.
+## Usage
-## Using
-
-### Required Maven Changes
-
-Add the following dependency:
+Add the dependency:
```xml
@@ -37,10 +25,7 @@ Add the following dependency:
```
-### Code Changes
-
-You enable the runtime compiler by configuring the instance to use `MachineFactoryCompiler::compile` as the machine factory instead
-of the default `InterpreterMachine`.
+Then configure the instance to use `MachineFactoryCompiler::compile` as its machine factory:
-### Loading and Instantiating Wasm Modules
-
-First your Wasm module must be loaded from disk and then instantiated. Let's [download a test module](https://raw.githubusercontent.com/bytecodealliance/endive/main/wasm-corpus/src/main/resources/compiled/iterfact.wat.wasm) .
-This module contains some code to compute factorial:
+## Load and instantiate a module
-Download from the link or with curl:
+[Download a test module](https://raw.githubusercontent.com/bytecodealliance/endive/main/wasm-corpus/src/main/resources/compiled/iterfact.wat.wasm) that computes a factorial, from the link or with curl:
```bash
curl https://raw.githubusercontent.com/bytecodealliance/endive/main/wasm-corpus/src/main/resources/compiled/iterfact.wat.wasm > factorial.wasm
@@ -56,7 +45,7 @@ docs.FileOps.copyFromWasmCorpus("iterfact.wat.wasm", "factorial.wasm");
```
-->
-Load this module and instantiate it:
+Parse it and create an instance:
```java
import run.endive.runtime.ExportFunction;
@@ -70,25 +59,21 @@ var module = Parser.parse(new File("./factorial.wasm"));
Instance instance = Instance.builder(module).build();
```
+The module is inert code; the instance is a virtual machine ready to execute it.
+
:::note[Threading]
`Instance` and `Store` are not thread-safe. Create separate instances per thread, or synchronize access externally. Memory operations (used by the Wasm threads proposal) are thread-safe.
:::
-You can think of the `module` as of inert code, and the `instance`
-is the run-time representation of that code: a virtual machine ready to execute.
-
-### Invoking a Wasm Function
+## Call a function
-Wasm modules, like all code modules, can export functions to the outside
-world. This module exports a function called `"iterFact"`.
-We can get a handle to this function using `Instance#export(String)`:
+The module exports a function called `iterFact`. Get a handle to it with `Instance#export(String)`:
```java
ExportFunction iterFact = instance.export("iterFact");
```
-`iterFact` can be invoked with the `apply()` method. We must map any Java types to raw `long`s and do the reverse
-when we want to go back to Java.
+Call it with `apply()`. Arguments and results are raw `long`s, so map Java types yourself on both sides:
```java
var result = iterFact.apply(5)[0];
@@ -102,5 +87,5 @@ docs.FileOps.writeResult("docs", "index.md.result", "" + result);
-->
:::note
-Functions in Wasm can return multiple values, hence the array. This function only returns one value, so we take the first value.
+Wasm functions can return multiple values, hence the array. This one returns a single value, so we take the first.
:::
diff --git a/docs/docs/migration/from-chicory.md b/docs/docs/migration/from-chicory.md
index 6f6cedce6..b555e75f6 100644
--- a/docs/docs/migration/from-chicory.md
+++ b/docs/docs/migration/from-chicory.md
@@ -4,8 +4,6 @@ sidebar_label: From Chicory
title: Migrating from Chicory
---
-# Migrating from Chicory to Endive
-
Endive is a fork of [Chicory](https://github.com/dylibso/chicory) by Dylibso, Inc.
This guide documents all breaking changes for users migrating from Chicory.
diff --git a/docs/docs/security/best-practices.md b/docs/docs/security/best-practices.md
index 4b0d0c671..9f0ca413b 100644
--- a/docs/docs/security/best-practices.md
+++ b/docs/docs/security/best-practices.md
@@ -4,8 +4,6 @@ sidebar_label: Best Practices
title: Security Best Practices
---
-# Security Best Practices
-
Practical guidance for running Wasm modules securely with Endive.
## Writing Safe Host Functions
@@ -15,7 +13,7 @@ Host functions are the primary attack surface β they cross the sandbox boundar
**Always validate arguments from Wasm:**
```java title="Example"
-HostFunction readMemory = (Instance instance, long... args) -> {
+WasmFunctionHandle readMemory = (Instance instance, long... args) -> {
int offset = (int) args[0];
int length = (int) args[1];
@@ -61,20 +59,9 @@ var options = WasiOptions.builder()
Wasm modules can consume unbounded CPU and memory. Always set limits when running untrusted code.
-**CPU timeout via thread interruption:**
-
-```java title="Example"
-var executor = Executors.newSingleThreadExecutor();
-var future = executor.submit(() -> instance.export("run").apply());
-
-try {
- var result = future.get(5, TimeUnit.SECONDS);
-} catch (TimeoutException e) {
- future.cancel(true); // interrupts the Wasm execution
-}
-```
+Cap memory growth with `withMemoryLimits`; see [Limiting memory growth](/docs/core/memory#limiting-memory-growth).
-See [CPU Limits](/docs/advanced/cpu-limits) for more patterns.
+Run untrusted code under a timeout and interrupt it when the deadline passes; see [CPU Limits](/docs/security/cpu-limits) for the patterns.
## Compiler Security
@@ -86,7 +73,7 @@ The runtime and build-time compilers translate Wasm to JVM bytecode for performa
## Dependency Supply Chain
-- All Endive dependencies are checked against the [Bytecode Alliance allowed license list](/docs/security/overview) in CI
+- All Endive dependencies are checked in CI against the Bytecode Alliance allowed license list
- Dependency vulnerabilities are scanned nightly via OWASP Dependency-Check
- Dependabot is enabled for automated security updates
- All dependency updates are manually reviewed before merging
diff --git a/docs/docs/advanced/cpu-limits.md b/docs/docs/security/cpu-limits.md
similarity index 71%
rename from docs/docs/advanced/cpu-limits.md
rename to docs/docs/security/cpu-limits.md
index 04e60c384..fdc030204 100644
--- a/docs/docs/advanced/cpu-limits.md
+++ b/docs/docs/security/cpu-limits.md
@@ -1,21 +1,14 @@
---
-sidebar_position: 1
-sidebar_label: CPU
-title: CPU
+sidebar_position: 3
+sidebar_label: CPU Limits
+title: CPU Limits
---
-# Limiting CPU usage
-:::warning[Security Consideration]
-Wasm modules can contain infinite loops. When running untrusted code, always set execution timeouts via thread interruption or an ExecutorService with a deadline. Without timeouts, a malicious module can consume 100% CPU indefinitely.
-:::
-
-Often, when running untrusted user code in our infrastructure, we want to have strong guarantees around the termination of the program.
-
-To achieve this result there are, currently, two mechanisms in Endive:
+Wasm modules can loop forever, so untrusted code needs a timeout. Endive offers two mechanisms to bound execution.
## Interrupts
-Wasm modules executed using Endive honour the carrier thread interruption mechanism, thus you can leverage it to implement absolute timeouts:
+Endive checks the interrupt flag of the thread running the module, so interrupting that thread stops the execution with a `WasmInterruptedException`:
```bash
curl https://raw.githubusercontent.com/bytecodealliance/endive/main/wasm-corpus/src/main/resources/compiled/infinite-loop.c.wasm > infinite-loop.wasm
@@ -61,6 +54,7 @@ Or using an `ExecutorService`:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
ExecutorService service = Executors.newSingleThreadExecutor();
var future = service.submit(() -> function.apply());
@@ -86,6 +80,6 @@ var instance =
diff --git a/docs/docs/security/overview.md b/docs/docs/security/overview.md
index ed35bb4c3..eb984a99c 100644
--- a/docs/docs/security/overview.md
+++ b/docs/docs/security/overview.md
@@ -4,8 +4,6 @@ sidebar_label: Security Model
title: Security Model
---
-# Security Model
-
Endive executes WebAssembly modules inside the JVM. Understanding the security boundaries is essential when running untrusted code.
## The Wasm Sandbox
@@ -44,8 +42,8 @@ The critical trust boundary is between **Wasm guest code** and **host functions*
## What the Sandbox Does NOT Guarantee
-- **CPU limits**: Wasm modules can execute infinite loops. The host must enforce timeouts (see [CPU Limits](/docs/advanced/cpu-limits)).
-- **Memory growth limits**: Modules can request memory growth up to the declared maximum. The host should set appropriate limits.
+- **CPU limits**: Wasm modules can execute infinite loops. The host must enforce timeouts (see [CPU Limits](/docs/security/cpu-limits)).
+- **Memory growth limits**: Modules can request memory growth up to the declared maximum. The host should set appropriate limits (see [Limiting memory growth](/docs/core/memory#limiting-memory-growth)). Those limits cover linear memory only: WasmGC structs and arrays are allocated on the JVM heap.
- **Post-compilation verification**: The build-time and runtime compilers translate Wasm to JVM bytecode without a separate verification pass. For maximum assurance with untrusted code, prefer the interpreter.
- **Cache integrity**: The [directory-based compiler cache](/docs/execution/compiler-cache/#the-directory-cache) does not verify bytecode integrity on load. Protect cache directories with restrictive permissions.
diff --git a/docs/docs/wasi/index.md b/docs/docs/wasi/index.md
index 55b74a203..979b0b5af 100644
--- a/docs/docs/wasi/index.md
+++ b/docs/docs/wasi/index.md
@@ -1,14 +1,8 @@
---
sidebar_position: 50
sidebar_label: WASI P1
-title: Wasi Preview 1
+title: WASI Preview 1
---
-# WASI Preview 1
-
-:::warning[Security Consideration]
-WASI file access does not enforce path sandboxing by default. Always use a virtual filesystem (e.g., [ZeroFs](https://github.com/roastedroot/zerofs) or [Jimfs](https://github.com/google/jimfs)) to restrict guest access to pre-opened directories. Passing the host filesystem directly exposes all files the JVM process can access. See [Security Best Practices](/docs/security/best-practices).
-:::
-
```java
-import run.endive.log.SystemLogger;
import run.endive.wasi.WasiOptions;
import run.endive.wasi.WasiPreview1;
import run.endive.wasm.Parser;
@@ -73,7 +71,6 @@ import run.endive.runtime.Store;
import java.io.File;
-var logger = new SystemLogger();
// let's just use the default options for now
var options = WasiOptions.builder().build();
// create our instance of wasip1
@@ -122,7 +119,7 @@ var wasi = WasiPreview1.builder().withOptions(wasiOpts).build();
// greet-wasi is a rust program that greets the string passed in stdin
var store = new Store().addFunction(wasi.toHostFunctions());
// instantiating will execute the module if it's a WASI command-pattern module
-store.instantiate("hello-wasi", Parser.parse(new File("greet-wasi.wasm")));
+store.instantiate("greet-wasi", Parser.parse(new File("greet-wasi.wasm")));
// check that we output the greeting
@@ -150,7 +147,7 @@ a convenient shorthand for doing the same is:
var wasi = WasiOptions.builder().inheritSystemStreams().build()
```
-## arguments
+## Arguments
Especially when using CLIs, it's useful to provide command line arguments to the Wasm Module.
@@ -160,7 +157,7 @@ You can do that by using:
var wasi = WasiOptions.builder().withArguments(List.of("executable-name", "--more", "--options")).build();
```
-## environment variables
+## Environment variables
To expose environment variables to your WASI module you can list them in the options:
@@ -171,11 +168,9 @@ var wasi = WasiOptions.builder().
build();
```
-## disk
+## Disk
-We provide limited support for operations on the disk, we only test on a Virtual FileSystem and we encourage you to use the same. We use [Google's Jimfs](https://github.com/google/jimfs).
-
-Example code to use the disk looks like:
+File access goes through a virtual file system, which confines the guest to the directories you map. The example uses [Google's Jimfs](https://github.com/google/jimfs):