Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 175 additions & 0 deletions docs/docs/experimental/redline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
---
sidebar_position: 2
sidebar_label: Native Compilation (Redline)
title: Native Compilation with Redline
---
## Overview

:::warning[Experimental]
Redline is experimental, see [Why](why.md) for what that means for stability.
It also supports less of the WebAssembly specification than the other execution
modes, so check [Feature support](#feature-support) before adopting it.
:::

Redline compiles your Wasm module to native machine code using [Cranelift](https://cranelift.dev/),
instead of to JVM bytecode. Compilation happens at build time for every supported platform, and the
right one is selected at runtime.

It is a substitute for the [Build Time Compiler](../execution/build-time-compiler.md) only, not for
the interpreter or the [Runtime Compiler](../execution/runtime-compiler.md), and it is enabled on
the same Maven plugin. The bytecode is still generated, and is used whenever native code cannot be.

## Feature support

Redline supports:

* the core specification
* bulk memory
* tail call
* threads and atomics
* reference type instructions

Redline does **not** support:

* multi memory
* exception handling
* garbage collection
* typed function references
* SIMD
* passing `externref` values to and from host functions

If your module uses anything from the second list the build fails. There is no per function
fallback, so a single unsupported instruction stops the whole module from compiling.

Every other execution mode supports all of the above, with the exception of SIMD,
which is available only in the interpreter.

## Platform support

Native code is generated for six platforms:

| | x86_64 | aarch64 |
|---|---|---|
| **Linux** | yes | yes |
| **macOS** | yes | yes |
| **Windows** | yes | yes |

On any other platform your module still runs, using the compiled bytecode instead. See
[Falling back](#falling-back).

## Usage

Enable it on the compiler plugin:

```xml
<plugin>
<groupId>run.endive</groupId>
<artifactId>endive-compiler-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<name>org.acme.wasm.MyModule</name>
<wasmFile>src/main/resources/my.wasm</wasmFile>
<redlineExperimental>true</redlineExperimental>
</configuration>
</execution>
</executions>
</plugin>
```

and add a runner. This is the only dependency you need, everything else comes transitively:

```xml
<dependency>
<groupId>run.endive</groupId>
<artifactId>redline-runner-experimental</artifactId>
<version>${endive.version}</version>
</dependency>
```

`redline-runner-experimental` uses the Panama FFM API and requires Java 25 or later. On older
versions use `redline-runner-jffi-experimental`, which needs only Java 11:

```xml
<dependency>
<groupId>run.endive</groupId>
<artifactId>redline-runner-jffi-experimental</artifactId>
<version>${endive.version}</version>
</dependency>
```

If both are present, the Panama runner is used wherever the JDK supports it.

Your module is then used exactly as it would be without redline:

```text
try (var instance = MyModule.builder().build()) {
var f = instance.export("my_function");
}
```

## Falling back

When native code cannot be used, `builder()` falls back to the bytecode produced by the
[Build Time Compiler](../execution/build-time-compiler.md), which is always generated alongside it.
This happens on platforms outside the table above, or when no runner is on the classpath.

Your module keeps working either way, so the fallback is silent. To check which one you got:

```text
MyModule.nativeProvider().isPresent()
```

It is `true` when native code is in use and `false` when the bytecode is. `MyModule.safeBuilder()`
always uses the bytecode, which is useful for comparing the two.

## What to expect

**Jar size.** Native code is considerably larger than the Wasm it comes from, and by default one
copy is generated per platform. If you know where you deploy, list only those targets:

```xml
<redlineTargetsExperimental>
<target>x86_64-unknown-linux-gnu</target>
</redlineTargetsExperimental>
```

The available triples are `x86_64-unknown-linux-gnu`, `aarch64-unknown-linux-gnu`,
`x86_64-apple-darwin`, `aarch64-apple-darwin`, `x86_64-pc-windows-msvc` and
`aarch64-pc-windows-msvc`.

**Build time.** Compiling for every platform takes noticeably longer than the build time compiler
alone. Narrowing the target list helps here too.

**Imported memories and tables.** A memory or table you pass in through `ImportValues` has to be
created by the runner, otherwise the first call fails. Modules that declare their own memory,
including anything built for WASI, are unaffected.

Create it through the provider:

```text
var provider = MyModule.nativeProvider().orElseThrow();

var memory = provider.createMemory(new MemoryLimits(1, 2));
var table = provider.createImportTable(new Table(ValType.FuncRef, new TableLimits(1)), REF_NULL_VALUE);

var imports = ImportValues.builder()
.addMemory(new ImportMemory("env", "memory", memory))
.addTable(new ImportTable("env", "table", table))
.build();

try (var instance = MyModule.builder().withImportValues(imports).build()) {
var f = instance.export("my_function");
}
```

<!--
```java
//DEPS run.endive:docs-lib:999-SNAPSHOT

docs.FileOps.writeResult("docs/experimental", "redline.md.result", "empty");
```
-->
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
empty
Loading