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}. * *

Format: *

  *   [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]
+ *   [4 bytes: version (2)]
+ *   [modified UTF-8: target triple]
+ *   [4 bytes: image length]
+ *   [N bytes: image]
+ *   [4 bytes: function body count]
+ *   For each function body:
+ *     [4 bytes: body offset, -1 for uncompiled]
+ *     [4 bytes: entry trampoline offset, -1 for uncompiled]
+ *   [4 bytes: imported function count]
+ *   For each imported function:
+ *     [4 bytes: import trampoline offset]
+ *     [4 bytes: offset of the slot for its host stub address]
  * 
*/ public final class NativeCodeSerializer { private static final int MAGIC = 0x434C344A; // "CL4J" - private static final int VERSION = 1; + private static final int VERSION = 2; private NativeCodeSerializer() {} - public static void serialize(byte[][] code, OutputStream out) throws IOException { + public static void serialize(NativeCode 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.writeUTF(code.triple()); + dos.writeInt(code.image().length); + dos.write(code.image()); + dos.writeInt(code.functionBodyCount()); + for (int i = 0; i < code.functionBodyCount(); i++) { + dos.writeInt(code.bodyOffset(i)); + dos.writeInt(code.entryTrampolineOffset(i)); + } + dos.writeInt(code.importCount()); + for (int i = 0; i < code.importCount(); i++) { + dos.writeInt(code.importTrampolineOffset(i)); + dos.writeInt(code.importStubSlotOffset(i)); } dos.flush(); } - public static byte[][] deserialize(InputStream in) throws IOException { + public static NativeCode deserialize(InputStream in) throws IOException { DataInputStream dis = new DataInputStream(in); int magic = dis.readInt(); if (magic != MAGIC) { @@ -55,39 +65,67 @@ public static byte[][] deserialize(InputStream in) throws IOException { if (version != VERSION) { throw new IOException("Unsupported native code version: " + version); } + String triple = dis.readUTF(); + byte[] image = readBytes(dis, readCount(dis, "image length"), "image"); + int bodyCount = readCount(dis, "function body count"); + // Read before allocating, so a corrupt count fails on the truncated file + int[] offsets = readInts(dis, bodyCount * 2L, "function body offsets"); + int[] bodyOffsets = new int[bodyCount]; + int[] entryTrampolineOffsets = new int[bodyCount]; + for (int i = 0; i < bodyCount; i++) { + bodyOffsets[i] = offsets[i * 2]; + entryTrampolineOffsets[i] = offsets[i * 2 + 1]; + } + int importCount = readCount(dis, "imported function count"); + int[] importOffsets = readInts(dis, importCount * 2L, "import offsets"); + int[] importTrampolineOffsets = new int[importCount]; + int[] importStubSlotOffsets = new int[importCount]; + for (int i = 0; i < importCount; i++) { + importTrampolineOffsets[i] = importOffsets[i * 2]; + importStubSlotOffsets[i] = importOffsets[i * 2 + 1]; + } + try { + return new NativeCode( + triple, + image, + bodyOffsets, + entryTrampolineOffsets, + importTrampolineOffsets, + importStubSlotOffsets); + } catch (IllegalArgumentException e) { + throw new IOException("Invalid native code file: " + e.getMessage(), e); + } + } + + private static int readCount(DataInputStream dis, String what) throws IOException { int count = dis.readInt(); if (count < 0) { - throw new IOException("Invalid native code file: negative function count " + count); + throw new IOException("Invalid native code file: negative " + what + " " + count); } - // Collected rather than pre-allocated: a corrupt count would otherwise - // reserve up to 2^31 array slots before any read could reveal the file is - // truncated, turning a bad file into an OutOfMemoryError. - List 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"