Skip to content
Open
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
41 changes: 41 additions & 0 deletions .github/workflows/redline.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Redline CI

on:
push:
branches: [ main ]
pull_request:

jobs:
redline:
name: Redline (Java 25, ${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
steps:
- name: Checkout sources
uses: actions/checkout@v7
- name: Checkout testsuite
uses: actions/checkout@v7
with:
repository: WebAssembly/testsuite
path: testsuite
ref: 88e97b0f742f4c3ee01fea683da130f344dd7b02
- name: Set up Java
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '25'
cache: maven
- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-wasip1
- name: Build cranelift_bridge.wasm
working-directory: redline/wasm-build
run: make all
- name: Build and test redline
run: mvn -B clean install -Predline -Ddev
env:
MAVEN_OPTS: "-ea"
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ scripts/jmh-tmp
/main/

/*.wasm
redline/cranelift_bridge.wasm
redline/wasm-build/target/
.approval_tests_temp/

# mac-os files
Expand Down
27 changes: 27 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,26 @@
<artifactId>log</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>run.endive</groupId>
<artifactId>redline-api-experimental</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>run.endive</groupId>
<artifactId>redline-bridge-experimental</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>run.endive</groupId>
<artifactId>redline-compiler-experimental</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>run.endive</groupId>
<artifactId>redline-runner-experimental</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>run.endive</groupId>
<artifactId>runtime</artifactId>
Expand Down Expand Up @@ -1002,6 +1022,13 @@
</modules>
</profile>

<profile>
<id>redline</id>
<modules>
<module>redline</module>
</modules>
</profile>

<profile>
<id>default-all-modules</id>
<activation>
Expand Down
22 changes: 22 additions & 0 deletions redline/api/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>run.endive</groupId>
<artifactId>redline-parent-experimental</artifactId>
<version>999-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>redline-api-experimental</artifactId>
<packaging>jar</packaging>
<name>Endive - Redline API</name>
<description>Redline native compiler API</description>

<dependencies>
<dependency>
<groupId>run.endive</groupId>
<artifactId>wasm</artifactId>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package run.endive.redline.experimental.api;

public interface Interruptible {
void requestInterrupt();

void clearInterrupt();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package run.endive.redline.experimental.api;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
* Serializes/deserializes pre-compiled native code (byte[][]).
*
* <p>Format:
* <pre>
* [4 bytes: magic "CL4J"]
* [4 bytes: version (1)]
* [4 bytes: function count]
* For each function:
* [4 bytes: code length, 0 for null/uncompiled]
* [N bytes: native code]
* </pre>
*/
public final class NativeCodeSerializer {

private static final int MAGIC = 0x434C344A; // "CL4J"
private static final int VERSION = 1;

private NativeCodeSerializer() {}

public static void serialize(byte[][] code, OutputStream out) throws IOException {
DataOutputStream dos = new DataOutputStream(out);
dos.writeInt(MAGIC);
dos.writeInt(VERSION);
dos.writeInt(code.length);
for (byte[] func : code) {
if (func != null) {
dos.writeInt(func.length);
dos.write(func);
} else {
dos.writeInt(0);
}
}
dos.flush();
}

public static byte[][] deserialize(InputStream in) throws IOException {
DataInputStream dis = new DataInputStream(in);
int magic = dis.readInt();
if (magic != MAGIC) {
throw new IOException(
"Invalid native code file: bad magic 0x" + Integer.toHexString(magic));
}
int version = dis.readInt();
if (version != VERSION) {
throw new IOException("Unsupported native code version: " + version);
}
int count = dis.readInt();
byte[][] code = new byte[count][];
for (int i = 0; i < count; i++) {
int len = dis.readInt();
if (len > 0) {
code[i] = dis.readNBytes(len);
if (code[i].length != len) {
throw new IOException(
"Truncated native code for function "
+ i
+ ": expected "
+ len
+ " bytes, got "
+ code[i].length);
}
}
}
return code;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package run.endive.redline.experimental.api;

import java.util.Locale;
import java.util.Optional;

public enum RedlineTarget {
LINUX_X86_64("x86_64-unknown-linux-gnu", "x86_64-linux"),
LINUX_AARCH64("aarch64-unknown-linux-gnu", "aarch64-linux"),
MACOS_X86_64("x86_64-apple-darwin", "x86_64-darwin"),
MACOS_AARCH64("aarch64-apple-darwin", "aarch64-darwin"),
WINDOWS_X86_64("x86_64-pc-windows-msvc", "x86_64-windows"),
WINDOWS_AARCH64("aarch64-pc-windows-msvc", "aarch64-windows");

private final String triple;
private final String resourceSuffix;

RedlineTarget(String triple, String resourceSuffix) {
this.triple = triple;
this.resourceSuffix = resourceSuffix;
}

public String triple() {
return triple;
}

public String resourceSuffix() {
return resourceSuffix;
}

public static Optional<RedlineTarget> detectHost() {
String osName =
System.getProperty("endive.redline.os.name", System.getProperty("os.name", ""))
.toLowerCase(Locale.ROOT);
String arch =
System.getProperty("endive.redline.os.arch", System.getProperty("os.arch", ""))
.toLowerCase(Locale.ROOT);

boolean isAarch64 = arch.equals("aarch64") || arch.equals("arm64");

if (osName.contains("linux")) {
return Optional.of(isAarch64 ? LINUX_AARCH64 : LINUX_X86_64);
} else if (osName.contains("mac") || osName.contains("darwin")) {
return Optional.of(isAarch64 ? MACOS_AARCH64 : MACOS_X86_64);
} else if (osName.contains("windows")) {
return Optional.of(isAarch64 ? WINDOWS_AARCH64 : WINDOWS_X86_64);
}
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package run.endive.redline.experimental.api.internal;

/**
* Layout of the shared context buffer passed between Java and native compiled code.
*
* <pre>
* Offset Type Field Description
* ------ ----- ---------------- ------------------------------------------
* 0 i64 funcTablePtr Pointer to function pointer table
* 8 i64 trampolinePtr Upcall stub for CALL_INDIRECT fallback
* 16 i32 trapCode Trap code written by native pre-checks
* 20 i32 typeId CALL_INDIRECT: expected type index
* 24 i32 tableIdx CALL_INDIRECT: table index
* 28 i32 elemIdx CALL_INDIRECT: table element index
* 32 i32 argCount Number of call arguments
* 36 i32 memGrowDelta Page count delta for memory.grow
* 40 i64 argsPtr Pointer to separate args buffer
* 48 i64 stackLimit Stack pointer limit for call depth guard
* 56 i64 memmovePtr Pointer to libc memmove
* 64 i64 memsetPtr Pointer to libc memset
* 72 i64 interruptFlag Non-zero = interrupt requested
* 200 i64 globalsPtr Pointer to globals buffer
* 208 i64 memGrowPtr Upcall stub for memory.grow
* 216 i32 memoryPages Current memory page count
* 224 i64 memBaseAddr Current memory base address
* 232 i64 tablePtrs Pointer to array of table buffer pointers
* 240 i64 funcTypesPtr Pointer to funcTypes array (i32 per func)
* ------ ----- ---------------- ------------------------------------------
* Total: 248 bytes used, 256 allocated (CTX_SIZE)
* </pre>
*/
public final class CtxBuffer {

private CtxBuffer() {}

public static final int CTX_SIZE = 256;
public static final int ARGS_BUFFER_CAPACITY = 1024;

public static final int FUNC_TABLE_PTR = 0;
public static final int TRAMPOLINE_PTR = 8;

public static final int TRAP_CODE = 16;
public static final int TYPE_ID = 20;
public static final int TABLE_IDX = 24;
public static final int ELEM_IDX = 28;
public static final int ARG_COUNT = 32;
public static final int MEM_GROW_DELTA = 36;
public static final int ARGS_PTR = 40;
public static final int STACK_LIMIT = 48;
public static final int MEMMOVE_PTR = 56;
public static final int MEMSET_PTR = 64;
public static final int INTERRUPT_FLAG = 72;

public static final int GLOBALS_PTR = 200;
public static final int MEM_GROW_PTR = 208;
public static final int MEMORY_PAGES = 216;
public static final int MEM_BASE_ADDR = 224;
public static final int TABLE_PTRS = 232;
public static final int FUNC_TYPES_PTR = 240;

public static final int TRAP_NONE = 0;
public static final int TRAP_DIV_BY_ZERO = 1;
public static final int TRAP_INT_OVERFLOW = 2;
public static final int TRAP_UNREACHABLE = 3;
public static final int TRAP_TRUNC_OVERFLOW = 4;
public static final int TRAP_OOB = 5;
public static final int TRAP_CALL_STACK_EXHAUSTED = 6;
public static final int TRAP_TABLE_OOB = 7;
public static final int TRAP_UNDEFINED_ELEMENT = 8;
public static final int TRAP_INDIRECT_CALL_TYPE_MISMATCH = 9;
public static final int TRAP_UNINITIALIZED_ELEMENT = 10;
public static final int TRAP_TRUNC_NAN = 11;
public static final int TRAP_UNALIGNED_ATOMIC = 12;
public static final int TRAP_INTERRUPTED = 13;

public static final int TABLE_SIZE_OFFSET = 0;
public static final int TABLE_MAX_OFFSET = 4;
public static final int TABLE_ENTRIES_OFFSET = 8;
public static final int TABLE_ENTRY_SIZE = 16;
public static final int ENTRY_TYPE_IDX_OFFSET = 0;
public static final int ENTRY_FUNC_ID_OFFSET = 4;
public static final int ENTRY_FUNC_PTR_OFFSET = 8;

public static int argOffset(int i) {
return 8 * i;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package run.endive.redline.experimental.api.internal;

import java.util.HashMap;
import run.endive.wasm.WasmModule;
import run.endive.wasm.types.FunctionType;

/**
* Builds canonical type maps for call_indirect type checking.
* Structurally equal FunctionTypes get the same canonical index.
*/
public final class TypeMapUtils {

private TypeMapUtils() {}

public static int[] buildCanonicalTypeMap(WasmModule module) {
var ts = module.typeSection();
int count = ts.subTypeCount();
int[] map = new int[count];
var seen = new HashMap<FunctionType, Integer>();
for (int i = 0; i < count; i++) {
var type = ts.getType(i);
if (type instanceof FunctionType) {
FunctionType ft = (FunctionType) type;
Integer canonical = seen.get(ft);
if (canonical != null) {
map[i] = canonical;
} else {
seen.put(ft, i);
map[i] = i;
}
} else {
map[i] = i;
}
}
return map;
}
}
Loading
Loading