diff --git a/docs/docs/experimental/redline.md b/docs/docs/experimental/redline.md
index 74ede4c27..4e9d794ce 100644
--- a/docs/docs/experimental/redline.md
+++ b/docs/docs/experimental/redline.md
@@ -13,7 +13,8 @@ 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.
+right one is selected at runtime. Nothing is compiled at runtime: the runners only load the
+precompiled code, and Cranelift is not one of their dependencies.
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
diff --git a/pom.xml b/pom.xml
index 98ab3dca1..21ca5dbbf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -62,7 +62,7 @@
${project.version}. Bump to an immutable released tag before cutting a
release, so the published jars can be rebuilt from source.
-->
- 999.0.0-SNAPSHOT
+ 999.0.0-redline-202
2.22.0
3.21.0
2.22
diff --git a/redline/api/src/main/java/run/endive/redline/experimental/api/ImportFactory.java b/redline/api/src/main/java/run/endive/redline/experimental/api/ImportFactory.java
index bd962ce0f..08bc44323 100644
--- a/redline/api/src/main/java/run/endive/redline/experimental/api/ImportFactory.java
+++ b/redline/api/src/main/java/run/endive/redline/experimental/api/ImportFactory.java
@@ -26,7 +26,7 @@ private ImportFactory(NativeMachineFactoryProvider provider) {
* Picks the backend for a module holding this native code, or none. Called by the
* generated {@code imports()}.
*/
- public static ImportFactory forNativeCode(byte[][] nativeCode) {
+ public static ImportFactory forNativeCode(NativeCode nativeCode) {
if (nativeCode == null) {
return forBytecode();
}
diff --git a/redline/api/src/main/java/run/endive/redline/experimental/api/NativeCode.java b/redline/api/src/main/java/run/endive/redline/experimental/api/NativeCode.java
new file mode 100644
index 000000000..17336dcce
--- /dev/null
+++ b/redline/api/src/main/java/run/endive/redline/experimental/api/NativeCode.java
@@ -0,0 +1,111 @@
+package run.endive.redline.experimental.api;
+
+import java.util.Objects;
+
+/** Function bodies and trampolines of one module, compiled ahead of time for one target. */
+public final class NativeCode {
+
+ /** Offset of a function body that was not compiled. */
+ public static final int NOT_COMPILED = -1;
+
+ private final String triple;
+ private final byte[] image;
+ private final int[] bodyOffsets;
+ private final int[] entryTrampolineOffsets;
+ private final int[] importTrampolineOffsets;
+ // where the runner writes each import's host stub address, in native byte order
+ private final int[] importStubSlotOffsets;
+
+ public NativeCode(
+ String triple,
+ byte[] image,
+ int[] bodyOffsets,
+ int[] entryTrampolineOffsets,
+ int[] importTrampolineOffsets,
+ int[] importStubSlotOffsets) {
+ this.triple = Objects.requireNonNull(triple, "triple");
+ this.image = Objects.requireNonNull(image, "image");
+ this.bodyOffsets = bodyOffsets.clone();
+ this.entryTrampolineOffsets = entryTrampolineOffsets.clone();
+ this.importTrampolineOffsets = importTrampolineOffsets.clone();
+ this.importStubSlotOffsets = importStubSlotOffsets.clone();
+
+ if (this.bodyOffsets.length != this.entryTrampolineOffsets.length) {
+ throw new IllegalArgumentException(
+ this.bodyOffsets.length
+ + " function bodies but "
+ + this.entryTrampolineOffsets.length
+ + " entry trampolines");
+ }
+ for (int i = 0; i < this.bodyOffsets.length; i++) {
+ boolean compiled = this.bodyOffsets[i] != NOT_COMPILED;
+ if (compiled != (this.entryTrampolineOffsets[i] != NOT_COMPILED)) {
+ throw new IllegalArgumentException(
+ "Function body " + i + " and its entry trampoline disagree");
+ }
+ if (compiled) {
+ checkOffset("function body " + i, this.bodyOffsets[i]);
+ checkOffset("entry trampoline " + i, this.entryTrampolineOffsets[i]);
+ }
+ }
+ if (this.importTrampolineOffsets.length != this.importStubSlotOffsets.length) {
+ throw new IllegalArgumentException(
+ this.importTrampolineOffsets.length
+ + " import trampolines but "
+ + this.importStubSlotOffsets.length
+ + " stub slots");
+ }
+ for (int i = 0; i < this.importTrampolineOffsets.length; i++) {
+ checkOffset("import trampoline " + i, this.importTrampolineOffsets[i]);
+ checkOffset("import stub slot " + i, this.importStubSlotOffsets[i], 8);
+ }
+ }
+
+ private void checkOffset(String what, int offset) {
+ checkOffset(what, offset, 1);
+ }
+
+ private void checkOffset(String what, int offset, int size) {
+ if (offset < 0 || offset > image.length - size) {
+ throw new IllegalArgumentException(
+ what + " at " + offset + " is outside the " + image.length + " byte image");
+ }
+ }
+
+ public String triple() {
+ return triple;
+ }
+
+ /** The code image. Shared, not copied: callers must not modify it. */
+ public byte[] image() {
+ return image;
+ }
+
+ public int functionBodyCount() {
+ return bodyOffsets.length;
+ }
+
+ public boolean isCompiled(int bodyIndex) {
+ return bodyOffsets[bodyIndex] != NOT_COMPILED;
+ }
+
+ public int bodyOffset(int bodyIndex) {
+ return bodyOffsets[bodyIndex];
+ }
+
+ public int entryTrampolineOffset(int bodyIndex) {
+ return entryTrampolineOffsets[bodyIndex];
+ }
+
+ public int importCount() {
+ return importTrampolineOffsets.length;
+ }
+
+ public int importTrampolineOffset(int importIndex) {
+ return importTrampolineOffsets[importIndex];
+ }
+
+ public int importStubSlotOffset(int importIndex) {
+ return importStubSlotOffsets[importIndex];
+ }
+}
diff --git a/redline/api/src/main/java/run/endive/redline/experimental/api/NativeCodeSerializer.java b/redline/api/src/main/java/run/endive/redline/experimental/api/NativeCodeSerializer.java
index 3267aa619..64d09c102 100644
--- a/redline/api/src/main/java/run/endive/redline/experimental/api/NativeCodeSerializer.java
+++ b/redline/api/src/main/java/run/endive/redline/experimental/api/NativeCodeSerializer.java
@@ -5,46 +5,56 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.List;
+import java.nio.ByteBuffer;
/**
- * Serializes/deserializes pre-compiled native code (byte[][]).
+ * Serializes/deserializes pre-compiled {@link NativeCode}.
*
*
code = new ArrayList<>(Math.min(count, 1024));
- for (int i = 0; i < count; i++) {
- int len = dis.readInt();
- if (len < 0) {
- throw new IOException(
- "Invalid native code file: negative code length "
- + len
- + " for function "
- + i);
- }
- if (len == 0) {
- code.add(null);
- continue;
- }
- byte[] func = dis.readNBytes(len);
- if (func.length != len) {
- throw new IOException(
- "Truncated native code for function "
- + i
- + ": expected "
- + len
- + " bytes, got "
- + func.length);
- }
- code.add(func);
+ return count;
+ }
+
+ private static byte[] readBytes(DataInputStream dis, int len, String what) throws IOException {
+ byte[] bytes = dis.readNBytes(len);
+ if (bytes.length != len) {
+ throw new IOException(
+ "Truncated native code "
+ + what
+ + ": expected "
+ + len
+ + " bytes, got "
+ + bytes.length);
+ }
+ return bytes;
+ }
+
+ private static int[] readInts(DataInputStream dis, long count, String what) throws IOException {
+ if (count > Integer.MAX_VALUE / 4) {
+ throw new IOException("Invalid native code file: too many " + what);
}
- return code.toArray(new byte[0][]);
+ byte[] bytes = readBytes(dis, (int) count * 4, what);
+ int[] ints = new int[(int) count];
+ ByteBuffer.wrap(bytes).asIntBuffer().get(ints);
+ return ints;
}
}
diff --git a/redline/api/src/main/java/run/endive/redline/experimental/api/NativeMachineFactoryProvider.java b/redline/api/src/main/java/run/endive/redline/experimental/api/NativeMachineFactoryProvider.java
index f2632b263..195666d02 100644
--- a/redline/api/src/main/java/run/endive/redline/experimental/api/NativeMachineFactoryProvider.java
+++ b/redline/api/src/main/java/run/endive/redline/experimental/api/NativeMachineFactoryProvider.java
@@ -15,7 +15,7 @@
public interface NativeMachineFactoryProvider {
- Instance.Builder builder(WasmModule module, byte[][] precompiledCode);
+ Instance.Builder builder(WasmModule module, NativeCode precompiledCode);
Memory createMemory(MemoryLimits limits);
diff --git a/redline/api/src/main/java/run/endive/redline/experimental/api/internal/CtxBuffer.java b/redline/api/src/main/java/run/endive/redline/experimental/api/internal/CtxBuffer.java
index 7462e7896..420362c07 100644
--- a/redline/api/src/main/java/run/endive/redline/experimental/api/internal/CtxBuffer.java
+++ b/redline/api/src/main/java/run/endive/redline/experimental/api/internal/CtxBuffer.java
@@ -28,6 +28,8 @@
* ------ ----- ---------------- ------------------------------------------
* Total: 248 bytes used, 256 allocated (CTX_SIZE)
*
+ *
+ * Compiled code calls the host function pointers here with the platform ABI.
*/
public final class CtxBuffer {
diff --git a/redline/api/src/main/java/run/endive/redline/experimental/api/internal/NativeCodeCheck.java b/redline/api/src/main/java/run/endive/redline/experimental/api/internal/NativeCodeCheck.java
new file mode 100644
index 000000000..0efbc2e22
--- /dev/null
+++ b/redline/api/src/main/java/run/endive/redline/experimental/api/internal/NativeCodeCheck.java
@@ -0,0 +1,48 @@
+package run.endive.redline.experimental.api.internal;
+
+import run.endive.redline.experimental.api.NativeCode;
+import run.endive.wasm.WasmEngineException;
+import run.endive.wasm.WasmModule;
+import run.endive.wasm.types.ExternalType;
+
+/** Checks native code was compiled for this host and module before a runner links it. */
+public final class NativeCodeCheck {
+
+ private NativeCodeCheck() {}
+
+ public static NativeCode check(NativeCode code, WasmModule module) {
+ if (code == null) {
+ throw new WasmEngineException(
+ "No precompiled code provided. Precompile the module with the"
+ + " endive-compiler-maven-plugin and a Redline target.");
+ }
+ String host = RedlineTarget.detectHost().map(RedlineTarget::triple).orElse(null);
+ if (!code.triple().equals(host)) {
+ throw new WasmEngineException(
+ "Native code compiled for "
+ + code.triple()
+ + " cannot run on "
+ + (host == null ? "this unsupported platform" : host));
+ }
+ int bodies = module.codeSection().functionBodyCount();
+ if (code.functionBodyCount() != bodies) {
+ throw new WasmEngineException(
+ "Native code has "
+ + code.functionBodyCount()
+ + " function bodies but the module has "
+ + bodies);
+ }
+ long imports =
+ module.importSection().stream()
+ .filter(i -> i.importType() == ExternalType.FUNCTION)
+ .count();
+ if (code.importCount() != imports) {
+ throw new WasmEngineException(
+ "Native code has "
+ + code.importCount()
+ + " imported functions but the module has "
+ + imports);
+ }
+ return code;
+ }
+}
diff --git a/redline/api/src/test/java/run/endive/redline/experimental/api/NativeCodeSerializerTest.java b/redline/api/src/test/java/run/endive/redline/experimental/api/NativeCodeSerializerTest.java
new file mode 100644
index 000000000..8b92e6c70
--- /dev/null
+++ b/redline/api/src/test/java/run/endive/redline/experimental/api/NativeCodeSerializerTest.java
@@ -0,0 +1,99 @@
+package run.endive.redline.experimental.api;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import org.junit.jupiter.api.Test;
+
+public class NativeCodeSerializerTest {
+
+ private static NativeCode sample() {
+ byte[] image = new byte[64];
+ Arrays.fill(image, (byte) 0xCC);
+ return new NativeCode(
+ "x86_64-unknown-linux-gnu",
+ image,
+ new int[] {0, NativeCode.NOT_COMPILED, 16},
+ new int[] {32, NativeCode.NOT_COMPILED, 32},
+ new int[] {48},
+ new int[] {50});
+ }
+
+ private static byte[] serialize(NativeCode code) throws IOException {
+ var out = new ByteArrayOutputStream();
+ NativeCodeSerializer.serialize(code, out);
+ return out.toByteArray();
+ }
+
+ private static NativeCode deserialize(byte[] bytes) throws IOException {
+ return NativeCodeSerializer.deserialize(new ByteArrayInputStream(bytes));
+ }
+
+ @Test
+ public void roundTripKeepsTheLayout() throws IOException {
+ var code = deserialize(serialize(sample()));
+
+ assertEquals("x86_64-unknown-linux-gnu", code.triple());
+ assertArrayEquals(sample().image(), code.image());
+ assertEquals(3, code.functionBodyCount());
+ assertTrue(code.isCompiled(0));
+ assertFalse(code.isCompiled(1));
+ assertEquals(16, code.bodyOffset(2));
+ assertEquals(32, code.entryTrampolineOffset(2));
+ assertEquals(1, code.importCount());
+ assertEquals(48, code.importTrampolineOffset(0));
+ assertEquals(50, code.importStubSlotOffset(0));
+ }
+
+ @Test
+ public void aVersionOneFileIsRejected() throws IOException {
+ var out = new ByteArrayOutputStream();
+ var dos = new DataOutputStream(out);
+ dos.writeInt(0x434C344A);
+ dos.writeInt(1);
+ dos.writeInt(0);
+
+ var e = assertThrows(IOException.class, () -> deserialize(out.toByteArray()));
+ assertTrue(e.getMessage().contains("version"), e.getMessage());
+ }
+
+ @Test
+ public void aTruncatedFileIsRejected() throws IOException {
+ byte[] bytes = serialize(sample());
+
+ assertThrows(IOException.class, () -> deserialize(Arrays.copyOf(bytes, bytes.length - 1)));
+ assertThrows(IOException.class, () -> deserialize(Arrays.copyOf(bytes, 40)));
+ }
+
+ @Test
+ public void anOffsetOutsideTheImageIsRejected() throws IOException {
+ byte[] bytes = serialize(sample());
+ // the last int is the import stub slot offset, which must leave room for 8 bytes
+ bytes[bytes.length - 1] = (byte) 57;
+
+ var e = assertThrows(IOException.class, () -> deserialize(bytes));
+ assertTrue(e.getMessage().contains("import stub slot 0"), e.getMessage());
+ }
+
+ @Test
+ public void aBodyWithoutItsEntryTrampolineIsRejected() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ new NativeCode(
+ "x86_64-unknown-linux-gnu",
+ new byte[16],
+ new int[] {0},
+ new int[] {NativeCode.NOT_COMPILED},
+ new int[0],
+ new int[0]));
+ }
+}
diff --git a/redline/api/src/test/java/run/endive/redline/experimental/api/NativeMachineFactoryProviderTest.java b/redline/api/src/test/java/run/endive/redline/experimental/api/NativeMachineFactoryProviderTest.java
index bed580a2b..a0027eb7d 100644
--- a/redline/api/src/test/java/run/endive/redline/experimental/api/NativeMachineFactoryProviderTest.java
+++ b/redline/api/src/test/java/run/endive/redline/experimental/api/NativeMachineFactoryProviderTest.java
@@ -74,7 +74,7 @@ public void skipsAProviderCompiledForANewerJdk(@TempDir Path dir) throws Excepti
public static class LowProvider implements NativeMachineFactoryProvider {
@Override
- public Instance.Builder builder(WasmModule module, byte[][] precompiledCode) {
+ public Instance.Builder builder(WasmModule module, NativeCode precompiledCode) {
return null;
}
diff --git a/redline/api/src/test/java/run/endive/redline/experimental/api/internal/NativeCodeCheckTest.java b/redline/api/src/test/java/run/endive/redline/experimental/api/internal/NativeCodeCheckTest.java
new file mode 100644
index 000000000..c978782ee
--- /dev/null
+++ b/redline/api/src/test/java/run/endive/redline/experimental/api/internal/NativeCodeCheckTest.java
@@ -0,0 +1,72 @@
+package run.endive.redline.experimental.api.internal;
+
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+import org.junit.jupiter.api.Test;
+import run.endive.redline.experimental.api.NativeCode;
+import run.endive.wasm.Parser;
+import run.endive.wasm.WasmEngineException;
+import run.endive.wasm.WasmModule;
+
+public class NativeCodeCheckTest {
+
+ private static final WasmModule EMPTY_MODULE =
+ Parser.parse(new byte[] {0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00});
+
+ private static String hostTriple() {
+ var host = RedlineTarget.detectHost();
+ assumeTrue(host.isPresent(), "Host is not a Redline target");
+ return host.get().triple();
+ }
+
+ private static NativeCode code(String triple, int bodies, int imports) {
+ return new NativeCode(
+ triple,
+ new byte[16],
+ new int[bodies],
+ new int[bodies],
+ new int[imports],
+ new int[imports]);
+ }
+
+ @Test
+ public void matchingCodeIsAccepted() {
+ var code = code(hostTriple(), 0, 0);
+
+ assertSame(code, NativeCodeCheck.check(code, EMPTY_MODULE));
+ }
+
+ @Test
+ public void missingCodeIsRejected() {
+ assertThrows(WasmEngineException.class, () -> NativeCodeCheck.check(null, EMPTY_MODULE));
+ }
+
+ @Test
+ public void codeForAnotherTargetIsRejected() {
+ String other =
+ hostTriple().equals(RedlineTarget.LINUX_X86_64.triple())
+ ? RedlineTarget.LINUX_AARCH64.triple()
+ : RedlineTarget.LINUX_X86_64.triple();
+
+ var e =
+ assertThrows(
+ WasmEngineException.class,
+ () -> NativeCodeCheck.check(code(other, 0, 0), EMPTY_MODULE));
+ assertTrue(e.getMessage().contains(other), e.getMessage());
+ }
+
+ @Test
+ public void codeForAnotherModuleIsRejected() {
+ String host = hostTriple();
+
+ assertThrows(
+ WasmEngineException.class,
+ () -> NativeCodeCheck.check(code(host, 1, 0), EMPTY_MODULE));
+ assertThrows(
+ WasmEngineException.class,
+ () -> NativeCodeCheck.check(code(host, 0, 1), EMPTY_MODULE));
+ }
+}
diff --git a/redline/bridge/src/main/java/run/endive/redline/experimental/bridge/internal/CraneliftBridge.java b/redline/bridge/src/main/java/run/endive/redline/experimental/bridge/internal/CraneliftBridge.java
index 6de0eb2ac..80d445ae5 100644
--- a/redline/bridge/src/main/java/run/endive/redline/experimental/bridge/internal/CraneliftBridge.java
+++ b/redline/bridge/src/main/java/run/endive/redline/experimental/bridge/internal/CraneliftBridge.java
@@ -1,13 +1,10 @@
package run.endive.redline.experimental.bridge.internal;
import java.nio.charset.StandardCharsets;
-import java.util.HashMap;
-import java.util.Map;
import run.endive.runtime.ImportValues;
import run.endive.runtime.Instance;
import run.endive.wasi.WasiOptions;
import run.endive.wasi.WasiPreview1;
-import run.endive.wasm.types.FunctionType;
import run.endive.wasm.types.ValType;
public final class CraneliftBridge implements AutoCloseable {
@@ -76,160 +73,11 @@ public CraneliftBridge_ModuleExports exports() {
public byte[] compile() {
exports.compile();
- int codePtr = exports.getCodePtr();
- int codeLen = exports.getCodeLen();
- return exports.memory().readBytes(codePtr, codeLen);
+ return compiledCode();
}
- private void buildTrampolineSig(FunctionType funcType) {
- exports.beginTrampolineSig();
- exports.trampolineSigAddParam(TYPE_I64); // memBase
- exports.trampolineSigAddParam(TYPE_I64); // ctxPtr
- for (ValType param : funcType.params()) {
- exports.trampolineSigAddParam(valTypeToBridgeType(param));
- }
- if (funcType.returns().size() > 1) {
- exports.trampolineSigAddReturn(TYPE_I64);
- } else {
- for (ValType ret : funcType.returns()) {
- exports.trampolineSigAddReturn(valTypeToBridgeType(ret));
- }
- }
- }
-
- private byte[] readCompiledCode() {
- int ptr = exports.getCodePtr();
- int len = exports.getCodeLen();
- return exports.memory().readBytes(ptr, len);
- }
-
- private byte[] compileImportTrampolineRaw(long stubAddr) {
- exports.compileImportTrampoline((int) (stubAddr & 0xFFFFFFFFL), (int) (stubAddr >>> 32));
- return readCompiledCode();
- }
-
- public byte[] compileEntryTrampoline(FunctionType funcType) {
- buildTrampolineSig(funcType);
- exports.compileEntryTrampoline();
- return readCompiledCode();
- }
-
- public byte[] compileImportTrampoline(FunctionType funcType, long stubAddr) {
- buildTrampolineSig(funcType);
- return compileImportTrampolineRaw(stubAddr);
- }
-
- public byte[] compileStubTrampoline(long stubAddr, int[] paramTypes, int[] returnTypes) {
- exports.beginTrampolineSig();
- for (int p : paramTypes) {
- exports.trampolineSigAddParam(p);
- }
- for (int r : returnTypes) {
- exports.trampolineSigAddReturn(r);
- }
- return compileImportTrampolineRaw(stubAddr);
- }
-
- public static final class CompiledTrampolines {
- private final Map entryTrampolines;
- private final byte[][] importTrampolines;
- private final byte[] trampolineStubTramp;
- private final byte[] memGrowStubTramp;
- private final byte[] memmoveTramp;
- private final byte[] memsetTramp;
-
- private CompiledTrampolines(
- Map entryTrampolines,
- byte[][] importTrampolines,
- byte[] trampolineStubTramp,
- byte[] memGrowStubTramp,
- byte[] memmoveTramp,
- byte[] memsetTramp) {
- this.entryTrampolines = entryTrampolines;
- this.importTrampolines = importTrampolines;
- this.trampolineStubTramp = trampolineStubTramp;
- this.memGrowStubTramp = memGrowStubTramp;
- this.memmoveTramp = memmoveTramp;
- this.memsetTramp = memsetTramp;
- }
-
- public Map entryTrampolines() {
- return entryTrampolines;
- }
-
- public byte[][] importTrampolines() {
- return importTrampolines;
- }
-
- public byte[] trampolineStubTramp() {
- return trampolineStubTramp;
- }
-
- public byte[] memGrowStubTramp() {
- return memGrowStubTramp;
- }
-
- public byte[] memmoveTramp() {
- return memmoveTramp;
- }
-
- public byte[] memsetTramp() {
- return memsetTramp;
- }
-
- public long totalSize() {
- long size = 0;
- for (byte[] code : entryTrampolines.values()) {
- size += align(code.length, 16);
- }
- for (byte[] code : importTrampolines) {
- size += align(code.length, 16);
- }
- size += align(trampolineStubTramp.length, 16);
- size += align(memGrowStubTramp.length, 16);
- size += align(memmoveTramp.length, 16);
- size += align(memsetTramp.length, 16);
- return size;
- }
- }
-
- public CompiledTrampolines compileTrampolines(
- byte[][] compiledCode,
- FunctionType[] funcTypesByBody,
- FunctionType[] importTypes,
- long[] importStubAddrs,
- long trampolineStubAddr,
- long memGrowStubAddr,
- long memmoveAddr,
- long memsetAddr) {
-
- Map entryTrampolineCode = new HashMap<>();
- for (int i = 0; i < compiledCode.length; i++) {
- if (compiledCode[i] != null && !entryTrampolineCode.containsKey(funcTypesByBody[i])) {
- entryTrampolineCode.put(
- funcTypesByBody[i], compileEntryTrampoline(funcTypesByBody[i]));
- }
- }
-
- byte[][] importTrampolineCode = new byte[importTypes.length][];
- for (int i = 0; i < importTypes.length; i++) {
- importTrampolineCode[i] = compileImportTrampoline(importTypes[i], importStubAddrs[i]);
- }
-
- int[] i64Param = {TYPE_I64};
- int[] i64Return = {TYPE_I64};
- int[] i64x3Param = {TYPE_I64, TYPE_I64, TYPE_I64};
-
- return new CompiledTrampolines(
- entryTrampolineCode,
- importTrampolineCode,
- compileStubTrampoline(trampolineStubAddr, i64Param, i64Return),
- compileStubTrampoline(memGrowStubAddr, i64Param, i64Return),
- compileStubTrampoline(memmoveAddr, i64x3Param, i64Return),
- compileStubTrampoline(memsetAddr, i64x3Param, i64Return));
- }
-
- public static long align(long value, long alignment) {
- return (value + alignment - 1) & ~(alignment - 1);
+ /** The code produced by the last function or trampoline compilation. */
+ public byte[] compiledCode() {
+ return exports.memory().readBytes(exports.getCodePtr(), exports.getCodeLen());
}
}
diff --git a/redline/build-time-compiler/src/main/java/run/endive/redline/experimental/build/RedlineGenerator.java b/redline/build-time-compiler/src/main/java/run/endive/redline/experimental/build/RedlineGenerator.java
index 7b01e8e3e..425140f71 100644
--- a/redline/build-time-compiler/src/main/java/run/endive/redline/experimental/build/RedlineGenerator.java
+++ b/redline/build-time-compiler/src/main/java/run/endive/redline/experimental/build/RedlineGenerator.java
@@ -69,7 +69,7 @@ public void generateNativeCode() throws IOException {
() ->
new IllegalArgumentException(
"Unknown target triple: " + triple));
- byte[][] compiledCode = NativeCompiler.compileAll(triple, module);
+ var compiledCode = NativeCompiler.compile(triple, module);
var nativeFile =
resourceDir.resolve(baseName + "." + target.resourceSuffix() + ".native");
@@ -91,6 +91,7 @@ public void extendGeneratedSources() throws IOException {
var cu = StaticJavaParser.parse(sourceFile);
var type = cu.getClassByName(baseName).orElseThrow();
+ cu.addImport("run.endive.redline.experimental.api.NativeCode");
cu.addImport("run.endive.redline.experimental.api.NativeCodeSerializer");
cu.addImport("run.endive.redline.experimental.api.ImportFactory");
cu.addImport("run.endive.redline.experimental.api.NativeMachineFactoryProvider");
@@ -115,9 +116,9 @@ private static void generateNativeCodeHolderInnerClass(
// Generates:
//
// private static class NativeCodeHolder {
- // static final byte[][] CODE;
+ // static final NativeCode CODE;
// static {
- // byte[][] loaded = null;
+ // NativeCode loaded = null;
// var host = RedlineTarget.detectHost().orElse(null);
// if (host != null) {
// String resource = "." + host.resourceSuffix() + ".native";
@@ -149,14 +150,14 @@ private static void generateNativeCodeHolderInnerClass(
type.addMember(holderClass);
holderClass.addField(
- parseType("byte[][]"), "CODE", Modifier.Keyword.STATIC, Modifier.Keyword.FINAL);
+ parseType("NativeCode"), "CODE", Modifier.Keyword.STATIC, Modifier.Keyword.FINAL);
- // byte[][] loaded = null;
+ // NativeCode loaded = null;
var loadedVar =
new ExpressionStmt(
new VariableDeclarationExpr(
new VariableDeclarator(
- parseType("byte[][]"), "loaded", new NullLiteralExpr())));
+ parseType("NativeCode"), "loaded", new NullLiteralExpr())));
// var host = RedlineTarget.detectHost().orElse(null);
var detectHost =
@@ -260,13 +261,13 @@ private static void generateNativeCodeHolderInnerClass(
private static void generateLoadNativeCodeMethod(ClassOrInterfaceDeclaration type) {
// Generates:
//
- // public static byte[][] loadNativeCode() {
+ // public static NativeCode loadNativeCode() {
// return NativeCodeHolder.CODE;
// }
//
var method =
type.addMethod("loadNativeCode", Modifier.Keyword.PUBLIC, Modifier.Keyword.STATIC)
- .setType(parseType("byte[][]"));
+ .setType(parseType("NativeCode"));
method.createBody()
.addStatement(
new ReturnStmt(
diff --git a/redline/compiler/src/main/java/run/endive/redline/experimental/compiler/internal/EmitContext.java b/redline/compiler/src/main/java/run/endive/redline/experimental/compiler/internal/EmitContext.java
index 4245469ac..3c10e0eac 100644
--- a/redline/compiler/src/main/java/run/endive/redline/experimental/compiler/internal/EmitContext.java
+++ b/redline/compiler/src/main/java/run/endive/redline/experimental/compiler/internal/EmitContext.java
@@ -221,6 +221,7 @@ int getOrCreateMultiReturnSigRef(FunctionType ft) {
return sigRef;
}
+ /** Signature for the host stubs taking the ctx pointer: (i64) -> i64, platform ABI. */
int getOrCreateTrampolineSigRef() {
String key = "__trampoline__";
Integer cached = sigRefCache.get(key);
@@ -228,7 +229,7 @@ int getOrCreateTrampolineSigRef() {
return cached;
}
- bridge.exports().beginSig();
+ bridge.exports().beginPlatformSig();
bridge.exports().sigAddParam(CraneliftBridge.TYPE_I64);
bridge.exports().sigAddReturn(CraneliftBridge.TYPE_I64);
int sigRef = bridge.exports().endSig();
@@ -236,7 +237,7 @@ int getOrCreateTrampolineSigRef() {
return sigRef;
}
- /** Signature for memmove/memset: (i64, i64, i64) -> i64. */
+ /** Signature for memmove/memset: (i64, i64, i64) -> i64, platform ABI. */
int getOrCreateMemopSigRef() {
String key = "__memop__";
Integer cached = sigRefCache.get(key);
@@ -244,7 +245,7 @@ int getOrCreateMemopSigRef() {
return cached;
}
- bridge.exports().beginSig();
+ bridge.exports().beginPlatformSig();
bridge.exports().sigAddParam(CraneliftBridge.TYPE_I64); // dst ptr
bridge.exports().sigAddParam(CraneliftBridge.TYPE_I64); // src ptr / value
bridge.exports().sigAddParam(CraneliftBridge.TYPE_I64); // size
diff --git a/redline/compiler/src/main/java/run/endive/redline/experimental/compiler/internal/NativeCompiler.java b/redline/compiler/src/main/java/run/endive/redline/experimental/compiler/internal/NativeCompiler.java
index cafc8e092..157f9324d 100644
--- a/redline/compiler/src/main/java/run/endive/redline/experimental/compiler/internal/NativeCompiler.java
+++ b/redline/compiler/src/main/java/run/endive/redline/experimental/compiler/internal/NativeCompiler.java
@@ -1,15 +1,19 @@
package run.endive.redline.experimental.compiler.internal;
+import java.io.ByteArrayOutputStream;
import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
+import run.endive.redline.experimental.api.NativeCode;
import run.endive.redline.experimental.api.internal.CtxBuffer;
import run.endive.redline.experimental.api.internal.TypeMapUtils;
import run.endive.redline.experimental.bridge.internal.CraneliftBridge;
@@ -18,6 +22,7 @@
import run.endive.wasm.types.AnnotatedInstruction;
import run.endive.wasm.types.BlockType;
import run.endive.wasm.types.ExternalType;
+import run.endive.wasm.types.FunctionImport;
import run.endive.wasm.types.FunctionType;
import run.endive.wasm.types.OpCode;
import run.endive.wasm.types.ValType;
@@ -103,8 +108,83 @@ int branchArgCount() {
private static final ExecutorService POOL = Executors.newFixedThreadPool(THREAD_COUNT);
- public static byte[][] compileAll(String triple, WasmModule module) {
- return new NativeCompiler(null, triple, module).compileAll((boolean[]) null);
+ private static final int CODE_ALIGNMENT = 16;
+
+ /** Compiles every function body, and the trampolines linking them to the host. */
+ public static NativeCode compile(String triple, WasmModule module) {
+ var compiler = new NativeCompiler(null, triple, module);
+ return compiler.link(compiler.compileAll((boolean[]) null));
+ }
+
+ private NativeCode link(byte[][] bodies) {
+ var image = new ByteArrayOutputStream();
+ int[] bodyOffsets = new int[bodies.length];
+ int[] entryTrampolineOffsets = new int[bodies.length];
+ int[] importTrampolineOffsets = new int[numImports];
+ int[] importStubSlotOffsets = new int[numImports];
+ Arrays.fill(bodyOffsets, NativeCode.NOT_COMPILED);
+ Arrays.fill(entryTrampolineOffsets, NativeCode.NOT_COMPILED);
+
+ for (int i = 0; i < bodies.length; i++) {
+ if (bodies[i] != null) {
+ bodyOffsets[i] = append(image, bodies[i]);
+ }
+ }
+
+ try (var trampolineBridge = new CraneliftBridge()) {
+ trampolineBridge.init(triple);
+ var trampolines = new TrampolineCompiler(trampolineBridge);
+
+ Map entryTrampolineByType = new HashMap<>();
+ for (int i = 0; i < bodies.length; i++) {
+ if (bodies[i] != null) {
+ var funcType =
+ (FunctionType)
+ module.typeSection()
+ .getType(module.functionSection().getFunctionType(i));
+ entryTrampolineOffsets[i] =
+ entryTrampolineByType.computeIfAbsent(
+ funcType, t -> append(image, trampolines.entry(t)));
+ }
+ }
+
+ // Each import gets its own copy, as each is linked to a different stub
+ Map importTrampolineByType =
+ new HashMap<>();
+ int funcId = 0;
+ for (int i = 0; i < module.importSection().importCount(); i++) {
+ var imp = module.importSection().getImport(i);
+ if (imp.importType() == ExternalType.FUNCTION) {
+ var funcType =
+ (FunctionType)
+ module.typeSection()
+ .getType(((FunctionImport) imp).typeIndex());
+ var trampoline =
+ importTrampolineByType.computeIfAbsent(
+ funcType, trampolines::importCall);
+ importTrampolineOffsets[funcId] = append(image, trampoline.code);
+ importStubSlotOffsets[funcId] =
+ importTrampolineOffsets[funcId] + trampoline.stubSlotOffset;
+ funcId++;
+ }
+ }
+ }
+
+ return new NativeCode(
+ triple,
+ image.toByteArray(),
+ bodyOffsets,
+ entryTrampolineOffsets,
+ importTrampolineOffsets,
+ importStubSlotOffsets);
+ }
+
+ private static int append(ByteArrayOutputStream image, byte[] code) {
+ int offset = image.size();
+ image.write(code, 0, code.length);
+ int padding = -image.size() & (CODE_ALIGNMENT - 1);
+ image.write(new byte[padding], 0, padding);
+ return offset;
}
/**
diff --git a/redline/compiler/src/main/java/run/endive/redline/experimental/compiler/internal/TrampolineCompiler.java b/redline/compiler/src/main/java/run/endive/redline/experimental/compiler/internal/TrampolineCompiler.java
new file mode 100644
index 000000000..b3f0a1731
--- /dev/null
+++ b/redline/compiler/src/main/java/run/endive/redline/experimental/compiler/internal/TrampolineCompiler.java
@@ -0,0 +1,57 @@
+package run.endive.redline.experimental.compiler.internal;
+
+import run.endive.redline.experimental.bridge.internal.CraneliftBridge;
+import run.endive.wasm.types.FunctionType;
+import run.endive.wasm.types.ValType;
+
+/** Compiles the trampolines between the platform ABI and compiled code's Tail convention. */
+final class TrampolineCompiler {
+
+ private final CraneliftBridge bridge;
+
+ TrampolineCompiler(CraneliftBridge bridge) {
+ this.bridge = bridge;
+ }
+
+ /** Platform ABI {@code (funcPtr, memBase, ctxPtr, args...)} to the function at funcPtr. */
+ byte[] entry(FunctionType funcType) {
+ beginSig(funcType);
+ bridge.exports().compileEntryTrampoline();
+ return bridge.compiledCode();
+ }
+
+ /** Tail convention {@code (memBase, ctxPtr, args...)} to an import's host stub. */
+ ImportTrampoline importCall(FunctionType funcType) {
+ beginSig(funcType);
+ int stubSlotOffset = bridge.exports().compileImportTrampoline();
+ return new ImportTrampoline(bridge.compiledCode(), stubSlotOffset);
+ }
+
+ static final class ImportTrampoline {
+ final byte[] code;
+ final int stubSlotOffset;
+
+ ImportTrampoline(byte[] code, int stubSlotOffset) {
+ this.code = code;
+ this.stubSlotOffset = stubSlotOffset;
+ }
+ }
+
+ private void beginSig(FunctionType funcType) {
+ var exports = bridge.exports();
+ exports.beginTrampolineSig();
+ exports.trampolineSigAddParam(CraneliftBridge.TYPE_I64); // memBase
+ exports.trampolineSigAddParam(CraneliftBridge.TYPE_I64); // ctxPtr
+ for (ValType param : funcType.params()) {
+ exports.trampolineSigAddParam(CraneliftBridge.valTypeToBridgeType(param));
+ }
+ if (funcType.returns().size() > 1) {
+ // Multi-return: single i64 dummy return (actual values in argsBuffer)
+ exports.trampolineSigAddReturn(CraneliftBridge.TYPE_I64);
+ } else {
+ for (ValType ret : funcType.returns()) {
+ exports.trampolineSigAddReturn(CraneliftBridge.valTypeToBridgeType(ret));
+ }
+ }
+ }
+}
diff --git a/redline/it/src/it/redline-e2e-panama/src/test/java/endive/test/RedlinePanamaE2eTest.java b/redline/it/src/it/redline-e2e-panama/src/test/java/endive/test/RedlinePanamaE2eTest.java
index 0edc35cbd..6ee7ad4a1 100644
--- a/redline/it/src/it/redline-e2e-panama/src/test/java/endive/test/RedlinePanamaE2eTest.java
+++ b/redline/it/src/it/redline-e2e-panama/src/test/java/endive/test/RedlinePanamaE2eTest.java
@@ -4,6 +4,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@@ -123,6 +124,18 @@ public void nativeCodeIsAvailable() {
AddModule.loadNativeCode(), "Native code should be available on this platform");
}
+ /** Native code is compiled at build time, so the compiler must not ship with the runner. */
+ @Test
+ public void compilerIsNotOnTheRuntimeClasspath() {
+ for (String name :
+ new String[] {
+ "run.endive.redline.experimental.bridge.internal.CraneliftBridge",
+ "run.endive.redline.experimental.compiler.internal.NativeCompiler"
+ }) {
+ assertThrows(ClassNotFoundException.class, () -> Class.forName(name), name);
+ }
+ }
+
@Test
public void bothBuildersProduceSameResults() {
try (var nativeInstance = AddModule.builder().build();
diff --git a/redline/it/src/it/redline-e2e/src/test/java/endive/test/RedlineE2eTest.java b/redline/it/src/it/redline-e2e/src/test/java/endive/test/RedlineE2eTest.java
index 735848570..4b83fd797 100644
--- a/redline/it/src/it/redline-e2e/src/test/java/endive/test/RedlineE2eTest.java
+++ b/redline/it/src/it/redline-e2e/src/test/java/endive/test/RedlineE2eTest.java
@@ -4,6 +4,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@@ -136,6 +137,18 @@ public void nativeCodeIsAvailable() {
AddModule.loadNativeCode(), "Native code should be available on this platform");
}
+ /** Native code is compiled at build time, so the compiler must not ship with the runner. */
+ @Test
+ public void compilerIsNotOnTheRuntimeClasspath() {
+ for (String name :
+ new String[] {
+ "run.endive.redline.experimental.bridge.internal.CraneliftBridge",
+ "run.endive.redline.experimental.compiler.internal.NativeCompiler"
+ }) {
+ assertThrows(ClassNotFoundException.class, () -> Class.forName(name), name);
+ }
+ }
+
@Test
public void bothBuildersProduceSameResults() {
try (var nativeInstance = AddModule.builder().build();
diff --git a/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/HostImportRoundTripTest.java b/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/HostImportRoundTripTest.java
index 1ff78e08a..9ac608a30 100644
--- a/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/HostImportRoundTripTest.java
+++ b/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/HostImportRoundTripTest.java
@@ -106,10 +106,9 @@ private static Instance buildInstance() {
return JffiNativeMachineFactory.builder(module)
.withImportValues(imports)
- .withCompilerFunction(
- m ->
- NativeCompiler.compileAll(
- RedlineTarget.detectHost().orElseThrow().triple(), m))
+ .withPrecompiledCode(
+ NativeCompiler.compile(
+ RedlineTarget.detectHost().orElseThrow().triple(), module))
.build();
}
}
diff --git a/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/HostThrowPropagationTest.java b/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/HostThrowPropagationTest.java
index be14266a4..c22e8340f 100644
--- a/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/HostThrowPropagationTest.java
+++ b/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/HostThrowPropagationTest.java
@@ -47,11 +47,9 @@ public void moduleStopsWhenAHostFunctionThrows() {
try (var instance =
JffiNativeMachineFactory.builder(module)
.withImportValues(imports)
- .withCompilerFunction(
- m ->
- NativeCompiler.compileAll(
- RedlineTarget.detectHost().orElseThrow().triple(),
- m))
+ .withPrecompiledCode(
+ NativeCompiler.compile(
+ RedlineTarget.detectHost().orElseThrow().triple(), module))
.build()) {
assertThrows(Boom.class, () -> instance.export("callBoom").apply());
assertEquals(
diff --git a/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/ImportedMutablesTest.java b/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/ImportedMutablesTest.java
index 12babcb4b..be3c7bb0b 100644
--- a/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/ImportedMutablesTest.java
+++ b/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/ImportedMutablesTest.java
@@ -140,12 +140,12 @@ private static ImportValues importsFor(
private static run.endive.runtime.Instance build(
Memory memory, TableInstance table, GlobalInstance counter) {
- return JffiNativeMachineFactory.builder(module())
+ var module = module();
+ return JffiNativeMachineFactory.builder(module)
.withImportValues(importsFor(memory, table, counter))
- .withCompilerFunction(
- m ->
- NativeCompiler.compileAll(
- RedlineTarget.detectHost().orElseThrow().triple(), m))
+ .withPrecompiledCode(
+ NativeCompiler.compile(
+ RedlineTarget.detectHost().orElseThrow().triple(), module))
.build();
}
}
diff --git a/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/LifecycleTest.java b/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/LifecycleTest.java
index a160367b2..5ac1da798 100644
--- a/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/LifecycleTest.java
+++ b/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/LifecycleTest.java
@@ -38,10 +38,9 @@ private static WasmModule parse() {
private static run.endive.runtime.Instance build(WasmModule module) {
return JffiNativeMachineFactory.builder(module)
- .withCompilerFunction(
- m ->
- NativeCompiler.compileAll(
- RedlineTarget.detectHost().orElseThrow().triple(), m))
+ .withPrecompiledCode(
+ NativeCompiler.compile(
+ RedlineTarget.detectHost().orElseThrow().triple(), module))
.build();
}
}
diff --git a/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/ReentrantStackGuardTest.java b/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/ReentrantStackGuardTest.java
index 527adceec..64c1193ff 100644
--- a/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/ReentrantStackGuardTest.java
+++ b/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/ReentrantStackGuardTest.java
@@ -83,11 +83,9 @@ private static void recurseThroughHost(boolean native_) {
try (var instance =
JffiNativeMachineFactory.builder(module)
.withImportValues(imports)
- .withCompilerFunction(
- m ->
- NativeCompiler.compileAll(
- RedlineTarget.detectHost().orElseThrow().triple(),
- m))
+ .withPrecompiledCode(
+ NativeCompiler.compile(
+ RedlineTarget.detectHost().orElseThrow().triple(), module))
.build()) {
instance.export("recurse").apply();
}
diff --git a/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/TableInitExprTest.java b/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/TableInitExprTest.java
index b593ccf5b..343426a18 100644
--- a/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/TableInitExprTest.java
+++ b/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/TableInitExprTest.java
@@ -22,11 +22,9 @@ public void tableComesUpHoldingItsInitialiser() {
try (var instance =
JffiNativeMachineFactory.builder(module)
- .withCompilerFunction(
- m ->
- NativeCompiler.compileAll(
- RedlineTarget.detectHost().orElseThrow().triple(),
- m))
+ .withPrecompiledCode(
+ NativeCompiler.compile(
+ RedlineTarget.detectHost().orElseThrow().triple(), module))
.build()) {
assertEquals(
42,
diff --git a/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/TableReleaseTest.java b/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/TableReleaseTest.java
index d571fb732..8e7441817 100644
--- a/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/TableReleaseTest.java
+++ b/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/TableReleaseTest.java
@@ -64,11 +64,9 @@ public void closingKeepsATableTheModuleImported() {
private static run.endive.runtime.Instance build(WasmModule module, ImportValues imports) {
var builder =
JffiNativeMachineFactory.builder(module)
- .withCompilerFunction(
- m ->
- NativeCompiler.compileAll(
- RedlineTarget.detectHost().orElseThrow().triple(),
- m));
+ .withPrecompiledCode(
+ NativeCompiler.compile(
+ RedlineTarget.detectHost().orElseThrow().triple(), module));
if (imports != null) {
builder.withImportValues(imports);
}
diff --git a/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/TrapPropagationTest.java b/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/TrapPropagationTest.java
index 2fc4e480a..65d5f8187 100644
--- a/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/TrapPropagationTest.java
+++ b/redline/runner-jffi-tests/src/test/java/run/endive/redline/experimental/runner/jffi/internal/TrapPropagationTest.java
@@ -51,10 +51,9 @@ private static WasmModule parse() {
private static run.endive.runtime.Instance build(WasmModule module) {
return JffiNativeMachineFactory.builder(module)
- .withCompilerFunction(
- m ->
- NativeCompiler.compileAll(
- RedlineTarget.detectHost().orElseThrow().triple(), m))
+ .withPrecompiledCode(
+ NativeCompiler.compile(
+ RedlineTarget.detectHost().orElseThrow().triple(), module))
.build();
}
}
diff --git a/redline/runner-jffi-tests/src/test/java/run/endive/testing/NativeInstanceBuilder.java b/redline/runner-jffi-tests/src/test/java/run/endive/testing/NativeInstanceBuilder.java
index 8942a5a7b..b57a9bc2a 100644
--- a/redline/runner-jffi-tests/src/test/java/run/endive/testing/NativeInstanceBuilder.java
+++ b/redline/runner-jffi-tests/src/test/java/run/endive/testing/NativeInstanceBuilder.java
@@ -17,10 +17,8 @@ private NativeInstanceBuilder(JffiNativeMachineFactory.Builder delegate) {
public static NativeInstanceBuilder builder(WasmModule module) {
var b = JffiNativeMachineFactory.builder(module);
- b.withCompilerFunction(
- m ->
- NativeCompiler.compileAll(
- RedlineTarget.detectHost().orElseThrow().triple(), m));
+ b.withPrecompiledCode(
+ NativeCompiler.compile(RedlineTarget.detectHost().orElseThrow().triple(), module));
return new NativeInstanceBuilder(b);
}
diff --git a/redline/runner-jffi/pom.xml b/redline/runner-jffi/pom.xml
index 17917a57c..4787243f9 100644
--- a/redline/runner-jffi/pom.xml
+++ b/redline/runner-jffi/pom.xml
@@ -18,10 +18,6 @@
run.endive
redline-api-experimental
-
- run.endive
- redline-bridge-experimental
-
run.endive
runtime
@@ -36,4 +32,24 @@
provided
+
+
+
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+
+
+
+
+
+ run.endive:redline-bridge-experimental
+ run.endive:redline-compiler-experimental
+
+
+
+
+
+
+
diff --git a/redline/runner-jffi/src/main/java/run/endive/redline/experimental/runner/jffi/JffiMachineFactoryProvider.java b/redline/runner-jffi/src/main/java/run/endive/redline/experimental/runner/jffi/JffiMachineFactoryProvider.java
index d28d4ecbf..2e6b9d808 100644
--- a/redline/runner-jffi/src/main/java/run/endive/redline/experimental/runner/jffi/JffiMachineFactoryProvider.java
+++ b/redline/runner-jffi/src/main/java/run/endive/redline/experimental/runner/jffi/JffiMachineFactoryProvider.java
@@ -1,5 +1,6 @@
package run.endive.redline.experimental.runner.jffi;
+import run.endive.redline.experimental.api.NativeCode;
import run.endive.redline.experimental.api.NativeMachineFactoryProvider;
import run.endive.runtime.GlobalInstance;
import run.endive.runtime.Instance;
@@ -14,7 +15,7 @@
public final class JffiMachineFactoryProvider implements NativeMachineFactoryProvider {
@Override
- public Instance.Builder builder(WasmModule module, byte[][] precompiledCode) {
+ public Instance.Builder builder(WasmModule module, NativeCode precompiledCode) {
return JffiNativeMachineFactory.builder(module)
.withPrecompiledCode(precompiledCode)
.toInstanceBuilder();
diff --git a/redline/runner-jffi/src/main/java/run/endive/redline/experimental/runner/jffi/JffiNativeMachineFactory.java b/redline/runner-jffi/src/main/java/run/endive/redline/experimental/runner/jffi/JffiNativeMachineFactory.java
index afc67eb02..8b7e1c466 100644
--- a/redline/runner-jffi/src/main/java/run/endive/redline/experimental/runner/jffi/JffiNativeMachineFactory.java
+++ b/redline/runner-jffi/src/main/java/run/endive/redline/experimental/runner/jffi/JffiNativeMachineFactory.java
@@ -3,7 +3,7 @@
import com.kenai.jffi.MemoryIO;
import java.util.ArrayList;
import java.util.List;
-import java.util.function.Function;
+import run.endive.redline.experimental.api.NativeCode;
import run.endive.redline.experimental.runner.jffi.internal.JffiNativeGlobalInstance;
import run.endive.redline.experimental.runner.jffi.internal.JffiNativeMachine;
import run.endive.redline.experimental.runner.jffi.internal.JffiNativeMemory;
@@ -25,20 +25,15 @@ public final class JffiNativeMachineFactory {
private static final MemoryIO MEM = MemoryIO.getInstance();
private final WasmModule module;
- private final byte[][] precompiledCode;
- private final Function compilerFunction;
+ private final NativeCode precompiledCode;
private final List nativeTables = new ArrayList<>();
private long globalsBufferAddr;
private int globalIndex;
private JffiNativeMachine nativeMachine;
- private JffiNativeMachineFactory(
- WasmModule module,
- byte[][] precompiledCode,
- Function compilerFunction) {
+ private JffiNativeMachineFactory(WasmModule module, NativeCode precompiledCode) {
this.module = module;
this.precompiledCode = precompiledCode;
- this.compilerFunction = compilerFunction;
int importGlobalCount =
(int)
@@ -99,20 +94,14 @@ public Machine compile(Instance instance) {
this.globalIndex = importGlobalCount;
this.nativeTables.clear();
this.nativeMachine =
- new JffiNativeMachine(
- instance,
- nativeTables,
- globalsBufferAddr,
- precompiledCode,
- compilerFunction);
+ new JffiNativeMachine(instance, nativeTables, globalsBufferAddr, precompiledCode);
return nativeMachine;
}
public static final class Builder {
private final WasmModule module;
- private byte[][] precompiledCode;
- private Function compilerFunction;
+ private NativeCode precompiledCode;
private ImportValues importValues;
private MemoryLimits memoryLimits;
private boolean start = true;
@@ -122,16 +111,11 @@ public static final class Builder {
this.module = module;
}
- public Builder withPrecompiledCode(byte[][] precompiledCode) {
+ public Builder withPrecompiledCode(NativeCode precompiledCode) {
this.precompiledCode = precompiledCode;
return this;
}
- public Builder withCompilerFunction(Function compilerFunction) {
- this.compilerFunction = compilerFunction;
- return this;
- }
-
public Builder withImportValues(ImportValues importValues) {
this.importValues = importValues;
return this;
@@ -153,7 +137,7 @@ public Builder withInitialize(boolean init) {
}
public Instance.Builder toInstanceBuilder() {
- var factory = new JffiNativeMachineFactory(module, precompiledCode, compilerFunction);
+ var factory = new JffiNativeMachineFactory(module, precompiledCode);
return Instance.builder(module)
.withMachineFactory(factory::compile)
.withTableFactory(factory::createTable)
diff --git a/redline/runner-jffi/src/main/java/run/endive/redline/experimental/runner/jffi/internal/JffiNativeMachine.java b/redline/runner-jffi/src/main/java/run/endive/redline/experimental/runner/jffi/internal/JffiNativeMachine.java
index d5d570744..7b55a50cc 100644
--- a/redline/runner-jffi/src/main/java/run/endive/redline/experimental/runner/jffi/internal/JffiNativeMachine.java
+++ b/redline/runner-jffi/src/main/java/run/endive/redline/experimental/runner/jffi/internal/JffiNativeMachine.java
@@ -12,15 +12,15 @@
import com.kenai.jffi.PageManager;
import com.kenai.jffi.Type;
import java.lang.ref.Reference;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
+import run.endive.redline.experimental.api.NativeCode;
import run.endive.redline.experimental.api.internal.CtxBuffer;
import run.endive.redline.experimental.api.internal.InterruptWatchdog;
-import run.endive.redline.experimental.api.internal.RedlineTarget;
+import run.endive.redline.experimental.api.internal.NativeCodeCheck;
import run.endive.redline.experimental.api.internal.TypeMapUtils;
-import run.endive.redline.experimental.bridge.internal.CraneliftBridge;
import run.endive.runtime.Instance;
import run.endive.runtime.Machine;
import run.endive.runtime.TrapException;
@@ -91,8 +91,6 @@ public final class JffiNativeMachine implements Machine {
private final FunctionType[] funcTypes; // wasm FunctionType per func
private final long codeRegionAddr;
private final int codeRegionOsPages;
- private long trampolineRegionAddr;
- private int trampolineRegionOsPages;
private final long ctxBufferAddr;
private final long funcTableAddr;
private final long funcTableSize; // byte size
@@ -125,10 +123,10 @@ public JffiNativeMachine(
Instance instance,
List sharedTables,
long sharedGlobalsBufferAddr,
- byte[][] precompiledCode,
- java.util.function.Function compilerFunction) {
+ NativeCode precompiledCode) {
this.instance = instance;
var module = instance.module();
+ NativeCode code = NativeCodeCheck.check(precompiledCode, module);
this.numImports =
(int)
module.importSection().stream()
@@ -187,11 +185,14 @@ public JffiNativeMachine(
this.tablePtrsArrayAddr = 0;
this.tablesInitialized = false;
- // Create CALL_INDIRECT trampoline closure
+ // Host stubs are called by compiled code with the platform ABI
this.trampolineHandle = createTrampolineStub();
-
- // Create memory.grow closure
this.memGrowHandle = createMemGrowStub();
+ for (int funcId = 0; funcId < numImports; funcId++) {
+ var funcType = instance.imports().function(funcId).functionType();
+ funcTypes[funcId] = funcType;
+ importHandles[funcId] = createImportStub(funcId, funcType);
+ }
// Write pointers to ctxBuffer
MEM.putLong(ctxBufferAddr + CtxBuffer.FUNC_TABLE_PTR, funcTableAddr);
@@ -204,32 +205,11 @@ public JffiNativeMachine(
MEM.putLong(ctxBufferAddr + CtxBuffer.MEMMOVE_PTR, MEMMOVE_ADDR);
MEM.putLong(ctxBufferAddr + CtxBuffer.MEMSET_PTR, MEMSET_ADDR);
- // Use pre-compiled code, or compile at runtime
- byte[][] compiledCode;
- if (precompiledCode != null) {
- compiledCode = precompiledCode;
- } else if (compilerFunction != null) {
- compiledCode = compilerFunction.apply(module);
- } else {
- throw new WasmEngineException(
- "No precompiled code provided. Use the redline-compiler-maven-plugin"
- + " to precompile, or use JffiNativeMachineFactory.builder(module)"
- + " for runtime compilation.");
- }
-
- // Calculate total code size
- long totalSize = 0;
- for (byte[] code : compiledCode) {
- if (code != null) {
- totalSize += CraneliftBridge.align(code.length, 16);
- }
- }
- totalSize = Math.max(totalSize, 4096);
- totalSize = CraneliftBridge.align(totalSize, 4096);
+ byte[] image = code.image();
// Allocate executable code region via PageManager
int osPageSize = (int) PM.pageSize();
- this.codeRegionOsPages = (int) ((totalSize + osPageSize - 1) / osPageSize);
+ this.codeRegionOsPages = (Math.max(image.length, 1) + osPageSize - 1) / osPageSize;
this.codeRegionAddr =
PM.allocatePages(codeRegionOsPages, PageManager.PROT_READ | PageManager.PROT_WRITE);
if (codeRegionAddr == 0 || codeRegionAddr == -1) {
@@ -237,151 +217,45 @@ public JffiNativeMachine(
}
try {
- // Copy code and track per-function addresses and types
- long offset = 0;
- long[] funcCodeAddrs = new long[compiledCode.length];
- FunctionType[] funcTypesByBody = new FunctionType[compiledCode.length];
-
- for (int i = 0; i < compiledCode.length; i++) {
- if (compiledCode[i] != null) {
- int funcId = numImports + i;
- MEM.putByteArray(
- codeRegionAddr + offset, compiledCode[i], 0, compiledCode[i].length);
-
- var funcType =
- (FunctionType)
- module.typeSection()
- .getType(module.functionSection().getFunctionType(i));
-
- long codePtr = codeRegionAddr + offset;
- funcCodeAddrs[i] = codePtr;
- funcTypesByBody[i] = funcType;
- funcTypes[funcId] = funcType;
-
- // Store native code address in function pointer table (Tail convention)
- MEM.putLong(funcTableAddr + (long) funcId * 8, codePtr);
-
- offset += CraneliftBridge.align(compiledCode[i].length, 16);
- }
+ MEM.putByteArray(codeRegionAddr, image, 0, image.length);
+ for (int funcId = 0; funcId < numImports; funcId++) {
+ // the slot may be unaligned
+ byte[] stubAddress =
+ ByteBuffer.allocate(8)
+ .order(ByteOrder.nativeOrder())
+ .putLong(importHandles[funcId].getAddress())
+ .array();
+ MEM.putByteArray(
+ codeRegionAddr + code.importStubSlotOffset(funcId), stubAddress, 0, 8);
}
-
- // Make code executable
PM.protectPages(
codeRegionAddr,
codeRegionOsPages,
PageManager.PROT_READ | PageManager.PROT_EXEC);
- // Create import closures (platform ABI)
- long[] importStubAddrs = new long[numImports];
- FunctionType[] importTypes = new FunctionType[numImports];
+ // Compiled code reaches imports through their trampolines
for (int funcId = 0; funcId < numImports; funcId++) {
- var importFunc = instance.imports().function(funcId);
- var funcType = importFunc.functionType();
- funcTypes[funcId] = funcType;
- Closure.Handle handle = createImportStub(funcId, funcType);
- importHandles[funcId] = handle;
- importStubAddrs[funcId] = handle.getAddress();
- importTypes[funcId] = funcType;
+ MEM.putLong(
+ funcTableAddr + (long) funcId * 8,
+ codeRegionAddr + code.importTrampolineOffset(funcId));
}
- // Compile ABI trampolines via Cranelift bridge
- try (var bridge = new CraneliftBridge()) {
- bridge.init(
- RedlineTarget.detectHost()
- .orElseThrow(
- () ->
- new WasmEngineException(
- "Unsupported platform for native"
- + " compilation"))
- .triple());
- var trampolines =
- bridge.compileTrampolines(
- compiledCode,
- funcTypesByBody,
- importTypes,
- importStubAddrs,
- trampolineHandle.getAddress(),
- memGrowHandle.getAddress(),
- MEMMOVE_ADDR,
- MEMSET_ADDR);
-
- long trampTotalSize = Math.max(trampolines.totalSize(), 4096);
- trampTotalSize = CraneliftBridge.align(trampTotalSize, 4096);
-
- // Allocate trampoline code region
- this.trampolineRegionOsPages =
- (int) ((trampTotalSize + osPageSize - 1) / osPageSize);
- this.trampolineRegionAddr =
- PM.allocatePages(
- trampolineRegionOsPages,
- PageManager.PROT_READ | PageManager.PROT_WRITE);
- if (trampolineRegionAddr == 0 || trampolineRegionAddr == -1) {
- throw new WasmEngineException("Failed to allocate trampoline code pages");
- }
-
- long trampOffset = 0;
-
- // Copy entry trampolines and record their addresses
- Map entryTrampolinePtrs = new HashMap<>();
- for (var entry : trampolines.entryTrampolines().entrySet()) {
- entryTrampolinePtrs.put(entry.getKey(), trampolineRegionAddr + trampOffset);
- trampOffset = copyCode(entry.getValue(), trampolineRegionAddr, trampOffset);
- }
-
- // Copy import trampolines and store addresses in funcTable
- for (int funcId = 0; funcId < numImports; funcId++) {
- byte[] code = trampolines.importTrampolines()[funcId];
+ for (int i = 0; i < code.functionBodyCount(); i++) {
+ if (code.isCompiled(i)) {
+ int funcId = numImports + i;
+ var funcType =
+ (FunctionType)
+ module.typeSection()
+ .getType(module.functionSection().getFunctionType(i));
+ funcTypes[funcId] = funcType;
MEM.putLong(
- funcTableAddr + (long) funcId * 8, trampolineRegionAddr + trampOffset);
- trampOffset = copyCode(code, trampolineRegionAddr, trampOffset);
- }
-
- // Copy internal stub trampolines and update ctxBuffer
- trampOffset =
- copyCodeAndUpdateCtx(
- trampolines.trampolineStubTramp(),
- trampolineRegionAddr,
- trampOffset,
- ctxBufferAddr,
- CtxBuffer.TRAMPOLINE_PTR);
- trampOffset =
- copyCodeAndUpdateCtx(
- trampolines.memGrowStubTramp(),
- trampolineRegionAddr,
- trampOffset,
- ctxBufferAddr,
- CtxBuffer.MEM_GROW_PTR);
- trampOffset =
- copyCodeAndUpdateCtx(
- trampolines.memmoveTramp(),
- trampolineRegionAddr,
- trampOffset,
- ctxBufferAddr,
- CtxBuffer.MEMMOVE_PTR);
- copyCodeAndUpdateCtx(
- trampolines.memsetTramp(),
- trampolineRegionAddr,
- trampOffset,
- ctxBufferAddr,
- CtxBuffer.MEMSET_PTR);
-
- PM.protectPages(
- trampolineRegionAddr,
- trampolineRegionOsPages,
- PageManager.PROT_READ | PageManager.PROT_EXEC);
-
- // Set per-function entry trampoline addresses and CallContexts
- for (int i = 0; i < compiledCode.length; i++) {
- if (compiledCode[i] != null) {
- int funcId = numImports + i;
- entryTrampolines[funcId] =
- new Function(
- entryTrampolinePtrs.get(funcTypesByBody[i]),
- createEntryTrampolineCallContext(funcTypesByBody[i]));
- }
+ funcTableAddr + (long) funcId * 8, codeRegionAddr + code.bodyOffset(i));
+ entryTrampolines[funcId] =
+ new Function(
+ codeRegionAddr + code.entryTrampolineOffset(i),
+ createEntryTrampolineCallContext(funcType));
}
}
-
} catch (WasmEngineException e) {
throw e;
} catch (Throwable e) {
@@ -428,9 +302,6 @@ public void close() {
if (codeRegionOsPages > 0 && codeRegionAddr != 0) {
PM.freePages(codeRegionAddr, codeRegionOsPages);
}
- if (trampolineRegionOsPages > 0 && trampolineRegionAddr != 0) {
- PM.freePages(trampolineRegionAddr, trampolineRegionOsPages);
- }
MEM.freeMemory(ctxBufferAddr);
MEM.freeMemory(funcTableAddr);
MEM.freeMemory(argsBufferAddr);
@@ -1012,19 +883,6 @@ private long invokeViaBufferWithTrampoline(
return INVOKER.invokeLong(func, buffer);
}
- // --- Trampoline copy helpers ---
-
- private static long copyCode(byte[] code, long regionAddr, long offset) {
- MEM.putByteArray(regionAddr + offset, code, 0, code.length);
- return offset + CraneliftBridge.align(code.length, 16);
- }
-
- private static long copyCodeAndUpdateCtx(
- byte[] code, long regionAddr, long offset, long ctxAddr, long ctxOffset) {
- MEM.putLong(ctxAddr + ctxOffset, regionAddr + offset);
- return copyCode(code, regionAddr, offset);
- }
-
// --- Main dispatch ---
@Override
diff --git a/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/HostImportRoundTripTest.java b/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/HostImportRoundTripTest.java
index 35c7c06ac..5a395bd39 100644
--- a/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/HostImportRoundTripTest.java
+++ b/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/HostImportRoundTripTest.java
@@ -106,10 +106,9 @@ private static Instance buildInstance() {
return NativeMachineFactory.builder(module)
.withImportValues(imports)
- .withCompilerFunction(
- m ->
- NativeCompiler.compileAll(
- RedlineTarget.detectHost().orElseThrow().triple(), m))
+ .withPrecompiledCode(
+ NativeCompiler.compile(
+ RedlineTarget.detectHost().orElseThrow().triple(), module))
.build();
}
}
diff --git a/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/HostThrowPropagationTest.java b/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/HostThrowPropagationTest.java
index fc519f1d1..9f2edc9db 100644
--- a/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/HostThrowPropagationTest.java
+++ b/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/HostThrowPropagationTest.java
@@ -47,11 +47,9 @@ public void moduleStopsWhenAHostFunctionThrows() {
try (var instance =
NativeMachineFactory.builder(module)
.withImportValues(imports)
- .withCompilerFunction(
- m ->
- NativeCompiler.compileAll(
- RedlineTarget.detectHost().orElseThrow().triple(),
- m))
+ .withPrecompiledCode(
+ NativeCompiler.compile(
+ RedlineTarget.detectHost().orElseThrow().triple(), module))
.build()) {
assertThrows(Boom.class, () -> instance.export("callBoom").apply());
assertEquals(
diff --git a/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/ImportedMutablesTest.java b/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/ImportedMutablesTest.java
index d155499d6..69705d029 100644
--- a/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/ImportedMutablesTest.java
+++ b/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/ImportedMutablesTest.java
@@ -140,12 +140,12 @@ private static ImportValues importsFor(
private static run.endive.runtime.Instance build(
Memory memory, TableInstance table, GlobalInstance counter) {
- return NativeMachineFactory.builder(module())
+ var module = module();
+ return NativeMachineFactory.builder(module)
.withImportValues(importsFor(memory, table, counter))
- .withCompilerFunction(
- m ->
- NativeCompiler.compileAll(
- RedlineTarget.detectHost().orElseThrow().triple(), m))
+ .withPrecompiledCode(
+ NativeCompiler.compile(
+ RedlineTarget.detectHost().orElseThrow().triple(), module))
.build();
}
}
diff --git a/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/LifecycleTest.java b/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/LifecycleTest.java
index 202b3b871..febb156db 100644
--- a/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/LifecycleTest.java
+++ b/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/LifecycleTest.java
@@ -38,10 +38,9 @@ private static WasmModule parse() {
private static run.endive.runtime.Instance build(WasmModule module) {
return NativeMachineFactory.builder(module)
- .withCompilerFunction(
- m ->
- NativeCompiler.compileAll(
- RedlineTarget.detectHost().orElseThrow().triple(), m))
+ .withPrecompiledCode(
+ NativeCompiler.compile(
+ RedlineTarget.detectHost().orElseThrow().triple(), module))
.build();
}
}
diff --git a/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/ReentrantStackGuardTest.java b/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/ReentrantStackGuardTest.java
index 3e766bc5a..2166d7b4a 100644
--- a/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/ReentrantStackGuardTest.java
+++ b/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/ReentrantStackGuardTest.java
@@ -83,11 +83,9 @@ private static void recurseThroughHost(boolean native_) {
try (var instance =
NativeMachineFactory.builder(module)
.withImportValues(imports)
- .withCompilerFunction(
- m ->
- NativeCompiler.compileAll(
- RedlineTarget.detectHost().orElseThrow().triple(),
- m))
+ .withPrecompiledCode(
+ NativeCompiler.compile(
+ RedlineTarget.detectHost().orElseThrow().triple(), module))
.build()) {
instance.export("recurse").apply();
}
diff --git a/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/TableInitExprTest.java b/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/TableInitExprTest.java
index 4657db2d2..1700f32fe 100644
--- a/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/TableInitExprTest.java
+++ b/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/TableInitExprTest.java
@@ -22,11 +22,9 @@ public void tableComesUpHoldingItsInitialiser() {
try (var instance =
NativeMachineFactory.builder(module)
- .withCompilerFunction(
- m ->
- NativeCompiler.compileAll(
- RedlineTarget.detectHost().orElseThrow().triple(),
- m))
+ .withPrecompiledCode(
+ NativeCompiler.compile(
+ RedlineTarget.detectHost().orElseThrow().triple(), module))
.build()) {
assertEquals(
42,
diff --git a/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/TrapPropagationTest.java b/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/TrapPropagationTest.java
index f6a7677bb..7175c35ed 100644
--- a/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/TrapPropagationTest.java
+++ b/redline/runner-tests/src/test/java/run/endive/redline/experimental/runner/internal/TrapPropagationTest.java
@@ -51,10 +51,9 @@ private static WasmModule parse() {
private static run.endive.runtime.Instance build(WasmModule module) {
return NativeMachineFactory.builder(module)
- .withCompilerFunction(
- m ->
- NativeCompiler.compileAll(
- RedlineTarget.detectHost().orElseThrow().triple(), m))
+ .withPrecompiledCode(
+ NativeCompiler.compile(
+ RedlineTarget.detectHost().orElseThrow().triple(), module))
.build();
}
}
diff --git a/redline/runner-tests/src/test/java/run/endive/testing/NativeInstanceBuilder.java b/redline/runner-tests/src/test/java/run/endive/testing/NativeInstanceBuilder.java
index 912e50a3c..fdb0746b8 100644
--- a/redline/runner-tests/src/test/java/run/endive/testing/NativeInstanceBuilder.java
+++ b/redline/runner-tests/src/test/java/run/endive/testing/NativeInstanceBuilder.java
@@ -17,10 +17,8 @@ private NativeInstanceBuilder(NativeMachineFactory.Builder delegate) {
public static NativeInstanceBuilder builder(WasmModule module) {
var b = NativeMachineFactory.builder(module);
- b.withCompilerFunction(
- m ->
- NativeCompiler.compileAll(
- RedlineTarget.detectHost().orElseThrow().triple(), m));
+ b.withPrecompiledCode(
+ NativeCompiler.compile(RedlineTarget.detectHost().orElseThrow().triple(), module));
return new NativeInstanceBuilder(b);
}
diff --git a/redline/runner/pom.xml b/redline/runner/pom.xml
index 420368929..3599b2e61 100644
--- a/redline/runner/pom.xml
+++ b/redline/runner/pom.xml
@@ -22,10 +22,6 @@
run.endive
redline-api-experimental
-
- run.endive
- redline-bridge-experimental
-
run.endive
runtime
@@ -35,4 +31,24 @@
wasm
+
+
+
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+
+
+
+
+
+ run.endive:redline-bridge-experimental
+ run.endive:redline-compiler-experimental
+
+
+
+
+
+
+
diff --git a/redline/runner/src/main/java/run/endive/redline/experimental/runner/NativeMachineFactory.java b/redline/runner/src/main/java/run/endive/redline/experimental/runner/NativeMachineFactory.java
index bc6af6140..d71e86ffe 100644
--- a/redline/runner/src/main/java/run/endive/redline/experimental/runner/NativeMachineFactory.java
+++ b/redline/runner/src/main/java/run/endive/redline/experimental/runner/NativeMachineFactory.java
@@ -4,7 +4,7 @@
import java.lang.foreign.MemorySegment;
import java.util.ArrayList;
import java.util.List;
-import java.util.function.Function;
+import run.endive.redline.experimental.api.NativeCode;
import run.endive.redline.experimental.runner.internal.NativeGlobalInstance;
import run.endive.redline.experimental.runner.internal.NativeMachine;
import run.endive.redline.experimental.runner.internal.NativeMemory;
@@ -25,20 +25,15 @@ public final class NativeMachineFactory {
private final Arena arena = Arena.ofShared();
private final WasmModule module;
- private final byte[][] precompiledCode;
- private final Function compilerFunction;
+ private final NativeCode precompiledCode;
private final List nativeTables = new ArrayList<>();
private MemorySegment globalsBuffer;
private int globalIndex;
private NativeMachine nativeMachine;
- private NativeMachineFactory(
- WasmModule module,
- byte[][] precompiledCode,
- Function compilerFunction) {
+ private NativeMachineFactory(WasmModule module, NativeCode precompiledCode) {
this.module = module;
this.precompiledCode = precompiledCode;
- this.compilerFunction = compilerFunction;
int importGlobalCount =
(int)
@@ -98,21 +93,14 @@ public Machine compile(Instance instance) {
this.globalIndex = importGlobalCount;
this.nativeTables.clear();
this.nativeMachine =
- new NativeMachine(
- instance,
- arena,
- nativeTables,
- globalsBuffer,
- precompiledCode,
- compilerFunction);
+ new NativeMachine(instance, arena, nativeTables, globalsBuffer, precompiledCode);
return nativeMachine;
}
public static final class Builder {
private final WasmModule module;
- private byte[][] precompiledCode;
- private Function compilerFunction;
+ private NativeCode precompiledCode;
private ImportValues importValues;
private MemoryLimits memoryLimits;
private boolean start = true;
@@ -122,16 +110,11 @@ public static final class Builder {
this.module = module;
}
- public Builder withPrecompiledCode(byte[][] precompiledCode) {
+ public Builder withPrecompiledCode(NativeCode precompiledCode) {
this.precompiledCode = precompiledCode;
return this;
}
- public Builder withCompilerFunction(Function compilerFunction) {
- this.compilerFunction = compilerFunction;
- return this;
- }
-
public Builder withImportValues(ImportValues importValues) {
this.importValues = importValues;
return this;
@@ -153,7 +136,7 @@ public Builder withInitialize(boolean init) {
}
public Instance.Builder toInstanceBuilder() {
- var factory = new NativeMachineFactory(module, precompiledCode, compilerFunction);
+ var factory = new NativeMachineFactory(module, precompiledCode);
return Instance.builder(module)
.withMachineFactory(factory::compile)
.withTableFactory(factory::createTable)
diff --git a/redline/runner/src/main/java/run/endive/redline/experimental/runner/PanamaMachineFactoryProvider.java b/redline/runner/src/main/java/run/endive/redline/experimental/runner/PanamaMachineFactoryProvider.java
index 2a775904d..b80038090 100644
--- a/redline/runner/src/main/java/run/endive/redline/experimental/runner/PanamaMachineFactoryProvider.java
+++ b/redline/runner/src/main/java/run/endive/redline/experimental/runner/PanamaMachineFactoryProvider.java
@@ -1,5 +1,6 @@
package run.endive.redline.experimental.runner;
+import run.endive.redline.experimental.api.NativeCode;
import run.endive.redline.experimental.api.NativeMachineFactoryProvider;
import run.endive.runtime.GlobalInstance;
import run.endive.runtime.Instance;
@@ -14,7 +15,7 @@
public final class PanamaMachineFactoryProvider implements NativeMachineFactoryProvider {
@Override
- public Instance.Builder builder(WasmModule module, byte[][] precompiledCode) {
+ public Instance.Builder builder(WasmModule module, NativeCode precompiledCode) {
return NativeMachineFactory.builder(module)
.withPrecompiledCode(precompiledCode)
.toInstanceBuilder();
diff --git a/redline/runner/src/main/java/run/endive/redline/experimental/runner/internal/NativeMachine.java b/redline/runner/src/main/java/run/endive/redline/experimental/runner/internal/NativeMachine.java
index 7051207fe..0c396e4a9 100644
--- a/redline/runner/src/main/java/run/endive/redline/experimental/runner/internal/NativeMachine.java
+++ b/redline/runner/src/main/java/run/endive/redline/experimental/runner/internal/NativeMachine.java
@@ -10,14 +10,11 @@
import java.lang.invoke.MethodType;
import java.lang.ref.Reference;
import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.function.Function;
+import run.endive.redline.experimental.api.NativeCode;
import run.endive.redline.experimental.api.internal.CtxBuffer;
import run.endive.redline.experimental.api.internal.InterruptWatchdog;
-import run.endive.redline.experimental.api.internal.RedlineTarget;
+import run.endive.redline.experimental.api.internal.NativeCodeCheck;
import run.endive.redline.experimental.api.internal.TypeMapUtils;
-import run.endive.redline.experimental.bridge.internal.CraneliftBridge;
import run.endive.runtime.Instance;
import run.endive.runtime.Machine;
import run.endive.runtime.TrapException;
@@ -45,6 +42,7 @@
public final class NativeMachine implements Machine {
private static final int CTX_SIZE = CtxBuffer.CTX_SIZE;
+ private static final long PAGE_SIZE = 4096;
// Conversion handles for adapting downcalls to uniform (MS, MS, long[]) → long.
// These use Wasm bit-reinterpretation (not numeric casts).
@@ -86,8 +84,6 @@ public final class NativeMachine implements Machine {
private final MethodHandle[] downcalls;
private final MemorySegment codeRegion;
private final long codeRegionSize;
- private MemorySegment trampolineRegion;
- private long trampolineRegionSize;
private final MemorySegment ctxBuffer;
private final MemorySegment funcTable;
private final MemorySegment argsBuffer;
@@ -113,11 +109,11 @@ public NativeMachine(
Arena arena,
java.util.List sharedTables,
MemorySegment sharedGlobalsBuffer,
- byte[][] precompiledCode,
- Function compilerFunction) {
+ NativeCode precompiledCode) {
this.instance = instance;
this.arena = arena;
var module = instance.module();
+ NativeCode code = NativeCodeCheck.check(precompiledCode, module);
this.numImports =
(int)
module.importSection().stream()
@@ -176,11 +172,14 @@ public NativeMachine(
this.tablePtrsArray = MemorySegment.NULL;
this.tablesInitialized = false;
- // Create CALL_INDIRECT trampoline upcall stub (kept for TABLE.INIT/ELEM.DROP)
+ // Host stubs are called by compiled code with the platform ABI
MemorySegment trampolineStub = createTrampolineStub();
-
- // Create memory.grow upcall stub
MemorySegment memGrowStub = createMemGrowStub();
+ MemorySegment[] importStubs = new MemorySegment[numImports];
+ for (int funcId = 0; funcId < numImports; funcId++) {
+ var funcType = instance.imports().function(funcId).functionType();
+ importStubs[funcId] = createImportStub(funcId, funcType);
+ }
// Write pointers to ctxBuffer
ctxBuffer.set(ValueLayout.JAVA_LONG, CtxBuffer.FUNC_TABLE_PTR, funcTable.address());
@@ -193,167 +192,44 @@ public NativeMachine(
ctxBuffer.set(ValueLayout.JAVA_LONG, CtxBuffer.MEMMOVE_PTR, PanamaExecutor.MEMMOVE_ADDR);
ctxBuffer.set(ValueLayout.JAVA_LONG, CtxBuffer.MEMSET_PTR, PanamaExecutor.MEMSET_ADDR);
- // Use pre-compiled code, or compile at runtime if explicitly enabled
- byte[][] compiledCode;
- if (precompiledCode != null) {
- compiledCode = precompiledCode;
- } else if (compilerFunction != null) {
- compiledCode = compilerFunction.apply(module);
- } else {
- throw new WasmEngineException(
- "No precompiled code provided. Use the redline-compiler-maven-plugin"
- + " to precompile, or use NativeMachineFactory.builder(module)"
- + " for runtime compilation.");
- }
-
- // mmap all compiled code into a single executable region
- long totalSize = 0;
- for (byte[] code : compiledCode) {
- if (code != null) {
- totalSize += CraneliftBridge.align(code.length, 16);
- }
- }
- totalSize = Math.max(totalSize, 4096);
- totalSize = CraneliftBridge.align(totalSize, 4096);
- this.codeRegionSize = totalSize;
+ byte[] image = code.image();
+ this.codeRegionSize = (Math.max(image.length, 1) + PAGE_SIZE - 1) & -PAGE_SIZE;
try {
- codeRegion = PanamaExecutor.mmapCode(totalSize);
- long offset = 0;
+ codeRegion = PanamaExecutor.mmapCode(codeRegionSize);
+ MemorySegment.copy(MemorySegment.ofArray(image), 0, codeRegion, 0, image.length);
+ for (int funcId = 0; funcId < numImports; funcId++) {
+ codeRegion.set(
+ ValueLayout.JAVA_LONG_UNALIGNED,
+ code.importStubSlotOffset(funcId),
+ importStubs[funcId].address());
+ }
+ PanamaExecutor.mprotectExec(codeRegion, codeRegionSize);
- // Track per-function code pointers and types for downcall creation after trampolines
- MemorySegment[] funcCodePtrs = new MemorySegment[compiledCode.length];
- FunctionType[] funcTypesByBody = new FunctionType[compiledCode.length];
+ // Compiled code reaches imports through their trampolines
+ for (int funcId = 0; funcId < numImports; funcId++) {
+ funcTable.setAtIndex(
+ ValueLayout.JAVA_LONG,
+ funcId,
+ codeRegion.address() + code.importTrampolineOffset(funcId));
+ }
- for (int i = 0; i < compiledCode.length; i++) {
- if (compiledCode[i] != null) {
+ for (int i = 0; i < code.functionBodyCount(); i++) {
+ if (code.isCompiled(i)) {
int funcId = numImports + i;
- MemorySegment.copy(
- MemorySegment.ofArray(compiledCode[i]),
- 0,
- codeRegion,
- offset,
- compiledCode[i].length);
-
var funcType =
(FunctionType)
module.typeSection()
.getType(module.functionSection().getFunctionType(i));
-
- MemorySegment codePtr = codeRegion.asSlice(offset);
- funcCodePtrs[i] = codePtr;
- funcTypesByBody[i] = funcType;
-
- // Store native code address in function pointer table (Tail convention)
- funcTable.set(ValueLayout.JAVA_LONG, (long) funcId * 8, codePtr.address());
-
- offset += CraneliftBridge.align(compiledCode[i].length, 16);
+ MemorySegment codePtr = codeRegion.asSlice(code.bodyOffset(i));
+ funcTable.setAtIndex(ValueLayout.JAVA_LONG, funcId, codePtr.address());
+ downcalls[funcId] =
+ createDowncallViaTrampoline(
+ codeRegion.asSlice(code.entryTrampolineOffset(i)),
+ codePtr,
+ funcType);
}
}
- PanamaExecutor.mprotectExec(codeRegion, totalSize);
-
- // Create import upcall stubs (platform ABI)
- MemorySegment[] importStubs = new MemorySegment[numImports];
- FunctionType[] importTypes = new FunctionType[numImports];
- for (int funcId = 0; funcId < numImports; funcId++) {
- var importFunc = instance.imports().function(funcId);
- var funcType = importFunc.functionType();
- importStubs[funcId] = createImportStub(funcId, funcType);
- importTypes[funcId] = funcType;
- downcalls[funcId] = null; // imports dispatch through call() directly
- }
-
- // Compile ABI trampolines via Cranelift bridge
- long[] importStubAddrs = new long[numImports];
- for (int i = 0; i < numImports; i++) {
- importStubAddrs[i] = importStubs[i].address();
- }
- try (var bridge = new CraneliftBridge()) {
- bridge.init(
- RedlineTarget.detectHost()
- .orElseThrow(
- () ->
- new WasmEngineException(
- "Unsupported platform for native"
- + " compilation"))
- .triple());
- var trampolines =
- bridge.compileTrampolines(
- compiledCode,
- funcTypesByBody,
- importTypes,
- importStubAddrs,
- trampolineStub.address(),
- memGrowStub.address(),
- PanamaExecutor.MEMMOVE_ADDR,
- PanamaExecutor.MEMSET_ADDR);
-
- long trampTotalSize = Math.max(trampolines.totalSize(), 4096);
- trampTotalSize = CraneliftBridge.align(trampTotalSize, 4096);
- this.trampolineRegionSize = trampTotalSize;
-
- // Mmap and copy trampoline code
- trampolineRegion = PanamaExecutor.mmapCode(trampTotalSize);
- long trampOffset = 0;
-
- // Copy entry trampolines and record their addresses
- Map entryTrampolinePtrs = new HashMap<>();
- for (var entry : trampolines.entryTrampolines().entrySet()) {
- entryTrampolinePtrs.put(entry.getKey(), trampolineRegion.asSlice(trampOffset));
- trampOffset = copyCode(entry.getValue(), trampolineRegion, trampOffset);
- }
-
- // Copy import trampolines and store addresses in funcTable
- for (int funcId = 0; funcId < numImports; funcId++) {
- byte[] code = trampolines.importTrampolines()[funcId];
- long addr = trampolineRegion.asSlice(trampOffset).address();
- trampOffset = copyCode(code, trampolineRegion, trampOffset);
- funcTable.set(ValueLayout.JAVA_LONG, (long) funcId * 8, addr);
- }
-
- // Copy internal stub trampolines and update ctxBuffer
- trampOffset =
- copyCodeAndUpdateCtx(
- trampolines.trampolineStubTramp(),
- trampolineRegion,
- trampOffset,
- ctxBuffer,
- CtxBuffer.TRAMPOLINE_PTR);
- trampOffset =
- copyCodeAndUpdateCtx(
- trampolines.memGrowStubTramp(),
- trampolineRegion,
- trampOffset,
- ctxBuffer,
- CtxBuffer.MEM_GROW_PTR);
- trampOffset =
- copyCodeAndUpdateCtx(
- trampolines.memmoveTramp(),
- trampolineRegion,
- trampOffset,
- ctxBuffer,
- CtxBuffer.MEMMOVE_PTR);
- copyCodeAndUpdateCtx(
- trampolines.memsetTramp(),
- trampolineRegion,
- trampOffset,
- ctxBuffer,
- CtxBuffer.MEMSET_PTR);
-
- PanamaExecutor.mprotectExec(trampolineRegion, trampTotalSize);
-
- // Create downcall handles via entry trampolines
- for (int i = 0; i < compiledCode.length; i++) {
- if (compiledCode[i] != null) {
- int funcId = numImports + i;
- MemorySegment trampolinePtr = entryTrampolinePtrs.get(funcTypesByBody[i]);
- downcalls[funcId] =
- createDowncallViaTrampoline(
- trampolinePtr, funcCodePtrs[i], funcTypesByBody[i]);
- }
- }
- }
-
} catch (Throwable e) {
throw new WasmEngineException("Failed to set up native code", e);
}
@@ -382,9 +258,6 @@ public void close() {
}
try {
PanamaExecutor.munmap(codeRegion, codeRegionSize);
- if (trampolineRegion != null && trampolineRegionSize > 0) {
- PanamaExecutor.munmap(trampolineRegion, trampolineRegionSize);
- }
} catch (Throwable e) {
throw new WasmEngineException("Failed to unmap native code", e);
}
@@ -980,19 +853,6 @@ private static WasmEngineException trapException(int trapCode) {
};
}
- // --- Trampoline copy helpers ---
-
- private static long copyCode(byte[] code, MemorySegment region, long offset) {
- MemorySegment.copy(MemorySegment.ofArray(code), 0, region, offset, code.length);
- return offset + CraneliftBridge.align(code.length, 16);
- }
-
- private static long copyCodeAndUpdateCtx(
- byte[] code, MemorySegment region, long offset, MemorySegment ctxBuf, long ctxOffset) {
- ctxBuf.set(ValueLayout.JAVA_LONG, ctxOffset, region.asSlice(offset).address());
- return copyCode(code, region, offset);
- }
-
// --- Main dispatch ---
private ValueLayout valTypeToLayout(ValType type) {
diff --git a/redline/wasm-build/src/lib.rs b/redline/wasm-build/src/lib.rs
index f73613c88..78e2efb5f 100644
--- a/redline/wasm-build/src/lib.rs
+++ b/redline/wasm-build/src/lib.rs
@@ -6,7 +6,8 @@
use cranelift_codegen::ir::condcodes::IntCC;
use cranelift_codegen::ir::types;
-use cranelift_codegen::ir::{AbiParam, AtomicRmwOp, BlockArg, BlockCall, Function, InstBuilder, MemFlagsData, Signature, UserFuncName};
+use cranelift_codegen::ir::{AbiParam, AtomicRmwOp, BlockArg, BlockCall, ExtFuncData, ExternalName, Function, InstBuilder, MemFlagsData, Signature, UserExternalName, UserFuncName};
+use cranelift_codegen::binemit::Reloc;
use cranelift_codegen::isa::{self, CallConv, TargetIsa};
use cranelift_codegen::settings::{self, Configurable};
use cranelift_codegen::Context;
@@ -1633,6 +1634,13 @@ pub extern "C" fn begin_sig() {
s().sig_builder = Some(Signature::new(CallConv::Tail));
}
+/// Like begin_sig, but for calling a host function with the platform ABI.
+#[no_mangle]
+pub extern "C" fn begin_platform_sig() {
+ let isa = unsafe { ISA.as_ref().expect("ISA not initialized") };
+ s().sig_builder = Some(Signature::new(isa.default_call_conv()));
+}
+
/// Add a parameter type to the current signature being built.
#[no_mangle]
pub extern "C" fn sig_add_param(wasm_type: u32) {
@@ -1737,6 +1745,9 @@ pub extern "C" fn compile() -> u32 {
let compiled = ctx
.compile(isa.as_ref(), &mut ControlPlane::default())
.expect("Compilation failed");
+ // Function bodies are loaded at any address, so they must not need patching
+ let relocs = compiled.buffer.relocs();
+ assert!(relocs.is_empty(), "Compiled code needs relocations: {:?}", relocs);
let code = compiled.code_buffer();
@@ -1796,7 +1807,7 @@ fn compile_trampoline(
cranelift_codegen::ir::SigRef,
&[cranelift_codegen::ir::Value],
) -> cranelift_codegen::ir::Inst,
-) -> u32 {
+) -> (u32, Vec<(u32, Reloc, i64)>) {
let isa = unsafe { ISA.as_ref().expect("ISA not initialized") };
let mut func = Function::with_name_signature(UserFuncName::user(0, 0), outer_sig);
let sig_ref = func.import_signature(inner_sig);
@@ -1820,10 +1831,18 @@ fn compile_trampoline(
.compile(isa.as_ref(), &mut ControlPlane::default())
.expect("Trampoline compilation failed");
+ let code = compiled.code_buffer().to_vec();
+ let len = code.len() as u32;
+ let relocs = compiled
+ .buffer
+ .relocs()
+ .iter()
+ .map(|r| (r.offset, r.kind, r.addend))
+ .collect();
unsafe {
- COMPILED_CODE = compiled.code_buffer().to_vec();
- COMPILED_CODE.len() as u32
+ COMPILED_CODE = code;
}
+ (len, relocs)
}
fn copy_sig(src: &Signature, conv: CallConv) -> Signature {
@@ -1846,25 +1865,37 @@ pub extern "C" fn compile_entry_trampoline() -> u32 {
for p in &tail_sig.params { outer_sig.params.push(p.clone()); }
for r in &tail_sig.returns { outer_sig.returns.push(r.clone()); }
- compile_trampoline(outer_sig, tail_sig, |builder, sig_ref, params| {
+ let (len, relocs) = compile_trampoline(outer_sig, tail_sig, |builder, sig_ref, params| {
builder.ins().call_indirect(sig_ref, params[0], ¶ms[1..])
- })
+ });
+ assert!(relocs.is_empty(), "Entry trampoline needs relocations: {:?}", relocs);
+ len
}
-/// Compile an import trampoline: Tail convention → platform ABI.
-/// Takes (memBase, ctxPtr, args...) with Tail convention,
-/// calls platform-ABI stub at baked-in address.
+/// Import trampoline whose stub address is patched at load; returns that 8-byte slot's offset.
#[no_mangle]
-pub extern "C" fn compile_import_trampoline(stub_addr_lo: u32, stub_addr_hi: u32) -> u32 {
+pub extern "C" fn compile_import_trampoline() -> u32 {
let isa = unsafe { ISA.as_ref().expect("ISA not initialized") };
let tail_sig = unsafe { TRAMPOLINE_SIG.take().expect("No trampoline sig") };
- let stub_addr = ((stub_addr_hi as u64) << 32) | (stub_addr_lo as u64);
let outer_sig = copy_sig(&tail_sig, CallConv::Tail);
let platform_sig = copy_sig(&tail_sig, isa.default_call_conv());
- compile_trampoline(outer_sig, platform_sig, |builder, sig_ref, params| {
- let stub_ptr = builder.ins().iconst(types::I64, stub_addr as i64);
+ let (_, relocs) = compile_trampoline(outer_sig, platform_sig, |builder, sig_ref, params| {
+ let name = builder
+ .func
+ .declare_imported_user_function(UserExternalName::new(0, 0));
+ let stub = builder.import_function(ExtFuncData {
+ name: ExternalName::user(name),
+ signature: sig_ref,
+ colocated: false,
+ patchable: false,
+ });
+ let stub_ptr = builder.ins().func_addr(types::I64, stub);
builder.ins().call_indirect(sig_ref, stub_ptr, params)
- })
+ });
+ match relocs.as_slice() {
+ [(offset, Reloc::Abs8, 0)] => *offset,
+ _ => panic!("Import trampoline needs unexpected relocations: {:?}", relocs),
+ }
}
diff --git a/redline/wkg.lock b/redline/wkg.lock
index 37ed30dcc..8f1c00874 100644
--- a/redline/wkg.lock
+++ b/redline/wkg.lock
@@ -15,3 +15,8 @@ digest = "sha256:f8c8b2d7c30646e0f3bae4c643ec3878bb505258fa75dd8e894b092595179e6
requirement = "=1.1.0"
version = "1.1.0"
digest = "sha256:f8c8b2d7c30646e0f3bae4c643ec3878bb505258fa75dd8e894b092595179e67"
+
+[[packages.versions]]
+requirement = "=999.0.0-redline-202"
+version = "999.0.0-redline-202"
+digest = "sha256:166b9828c619d834d66a49c73063ebbb8bdef2d94464b15f7eefc6a72e036ce5"