From 6fcae6bd7414961e125651c4b4c8ac2d9c86e5a5 Mon Sep 17 00:00:00 2001 From: andreatp Date: Mon, 31 Aug 2026 17:29:12 +0100 Subject: [PATCH] Document the experimental Redline native compiler --- docs/docs/experimental/redline.md | 185 ++++++++++++++++++ .../docs-experimental-redline.md.approved.txt | 1 + 2 files changed, 186 insertions(+) create mode 100644 docs/docs/experimental/redline.md create mode 100644 docs/tests/approvals/docs-experimental-redline.md.approved.txt diff --git a/docs/docs/experimental/redline.md b/docs/docs/experimental/redline.md new file mode 100644 index 000000000..c43929162 --- /dev/null +++ b/docs/docs/experimental/redline.md @@ -0,0 +1,185 @@ +--- +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 at runtime when the +architecture or operating system is not supported. + +## Feature support + +| Feature | Supported | +|---|---| +| Core specification | ✅ | +| Bulk memory | ✅ | +| Tail call | ✅ | +| Threads and atomics | ✅ | +| Reference type instructions | ✅ | +| Multi memory | ❌ | +| Exception handling | ❌ | +| Garbage collection | ❌ | +| Typed function references | ❌ | +| SIMD | ❌ | +| `externref` values to and from host functions | ❌ | + +If your module uses anything unsupported the build fails. There is no per function fallback, so a +single unsupported instruction stops the whole module from compiling. + +## Platform support + +Native code is generated for six platforms: + +| | x86_64 | aarch64 | +|---|---|---| +| **Linux** | ✅ | ✅ | +| **macOS** | ✅ | ✅ | +| **Windows** | ✅ | ✅ | + +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 + + run.endive + endive-compiler-maven-plugin + + + + compile + + + org.acme.wasm.MyModule + src/main/resources/my.wasm + true + + + + +``` + +and add a runner. This is the only dependency you need, everything else comes transitively: + +```xml + + run.endive + redline-runner-experimental + ${endive.version} + +``` + +`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 + + run.endive + redline-runner-jffi-experimental + ${endive.version} + +``` + +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. + +`nativeProvider()` and `imports()` exist only while redline is enabled. The rest of the generated +module is there either way. + +## What to expect + +**Jar size.** Native code is 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 + + x86_64-unknown-linux-gnu + +``` + +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, tables and globals.** Create anything you pass in through `ImportValues` from +`MyModule.imports()`, not by constructing it yourself. Redline reads these through a raw address, so +it can only use ones it made, and building one yourself fails with a message saying so. Modules that +declare their own memory, including anything built for WASI, are unaffected. + +```text +var imports = MyModule.imports(); + +var memory = imports.memory(new MemoryLimits(1, 2)); +var table = imports.table(new Table(ValType.FuncRef, new TableLimits(1)), REF_NULL_VALUE); +var counter = imports.global(Value.i32(0), MutabilityType.Var); + +var importValues = ImportValues.builder() + .addMemory(new ImportMemory("env", "memory", memory)) + .addTable(new ImportTable("env", "table", table)) + .addGlobal(new ImportGlobal("env", "counter", counter)) + .build(); + +try (var instance = MyModule.builder().withImportValues(importValues).build()) { + instance.export("my_function").apply(); + + // what the module wrote, on whichever backend it ran on + System.out.println(counter.getValue()); +} +``` + +The factory hands back native instances on a platform in the table above and ordinary ones +everywhere else, so this code is the same either way and needs no branch of its own. That matters +because it is decided at runtime: one jar can run natively on one machine and on bytecode on +another. + +`imports()` exists only while redline is enabled. Without it you build these the ordinary way, so +this is the one place where turning redline off means editing code. + + diff --git a/docs/tests/approvals/docs-experimental-redline.md.approved.txt b/docs/tests/approvals/docs-experimental-redline.md.approved.txt new file mode 100644 index 000000000..c6cac6926 --- /dev/null +++ b/docs/tests/approvals/docs-experimental-redline.md.approved.txt @@ -0,0 +1 @@ +empty