diff --git a/annotations/annotations/pom.xml b/annotations/annotations/pom.xml
index 97c9f7ab9..9de7b48d6 100644
--- a/annotations/annotations/pom.xml
+++ b/annotations/annotations/pom.xml
@@ -5,7 +5,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
../../pom.xml
annotations
diff --git a/annotations/it/pom.xml b/annotations/it/pom.xml
index f8db9a014..06e34437a 100644
--- a/annotations/it/pom.xml
+++ b/annotations/it/pom.xml
@@ -5,7 +5,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
../../pom.xml
annotations-it
diff --git a/annotations/processor/pom.xml b/annotations/processor/pom.xml
index 0966cba80..22da37246 100644
--- a/annotations/processor/pom.xml
+++ b/annotations/processor/pom.xml
@@ -5,7 +5,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
../../pom.xml
annotations-processor
diff --git a/bom/pom.xml b/bom/pom.xml
index afe9217ed..a2a833da8 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -5,7 +5,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
bom
pom
diff --git a/build-time-compiler-cli/pom.xml b/build-time-compiler-cli/pom.xml
index 4c4fb01b3..63cff6d66 100644
--- a/build-time-compiler-cli/pom.xml
+++ b/build-time-compiler-cli/pom.xml
@@ -5,7 +5,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
build-time-compiler-cli-experimental
jar
diff --git a/build-time-compiler/pom.xml b/build-time-compiler/pom.xml
index 89bf66cef..2ac621b1b 100644
--- a/build-time-compiler/pom.xml
+++ b/build-time-compiler/pom.xml
@@ -5,7 +5,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
../pom.xml
build-time-compiler
diff --git a/cli/pom.xml b/cli/pom.xml
index 75fa48fd6..a5c094171 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -5,7 +5,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
cli-experimental
jar
diff --git a/codegen/pom.xml b/codegen/pom.xml
index 721667b6d..8f812bc7f 100644
--- a/codegen/pom.xml
+++ b/codegen/pom.xml
@@ -5,7 +5,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
../pom.xml
codegen
diff --git a/compiler-maven-plugin/pom.xml b/compiler-maven-plugin/pom.xml
index a7ea02a6a..b81ded880 100644
--- a/compiler-maven-plugin/pom.xml
+++ b/compiler-maven-plugin/pom.xml
@@ -5,7 +5,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
../pom.xml
endive-compiler-maven-plugin
diff --git a/compiler-tests/pom.xml b/compiler-tests/pom.xml
index 3301170be..da36a47d3 100644
--- a/compiler-tests/pom.xml
+++ b/compiler-tests/pom.xml
@@ -5,7 +5,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
../pom.xml
compiler-tests
diff --git a/compiler/pom.xml b/compiler/pom.xml
index f59d81546..d23d04ede 100644
--- a/compiler/pom.xml
+++ b/compiler/pom.xml
@@ -5,7 +5,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
../pom.xml
compiler
diff --git a/compiler/src/main/java/run/endive/compiler/MachineFactoryCompiler.java b/compiler/src/main/java/run/endive/compiler/MachineFactoryCompiler.java
index f4a1e543b..fda5b22f5 100644
--- a/compiler/src/main/java/run/endive/compiler/MachineFactoryCompiler.java
+++ b/compiler/src/main/java/run/endive/compiler/MachineFactoryCompiler.java
@@ -5,6 +5,7 @@
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
@@ -18,6 +19,9 @@
import run.endive.runtime.Machine;
import run.endive.wasm.WasmEngineException;
import run.endive.wasm.WasmModule;
+import run.endive.wasm.types.CodeSection;
+import run.endive.wasm.types.ExternalType;
+import run.endive.wasm.types.FunctionBody;
/**
* Compiles WASM function bodies to JVM byte code that can be used as a machine factory for {@link Instance}'s.
@@ -85,6 +89,7 @@ public static Builder builder(WasmModule module) {
public static final class Builder {
private final WasmModule module;
private final run.endive.compiler.internal.Compiler.Builder compilerBuilder;
+ private boolean releaseFunctionBodies;
private Cache cache;
private Builder(WasmModule module) {
@@ -121,6 +126,20 @@ public Builder withMethodPrefixer(MethodPrefixer methodPrefixer) {
return this;
}
+ /**
+ * Drops the parsed instructions of every compiled function once compilation is done. The
+ * module handed to instances keeps each function's local types, and the functions left to
+ * the interpreter keep their instructions, but the compiled ones no longer hold their
+ * parsed code for as long as the module lives. Compiled machines never read it.
+ *
+ *
Modules loaded from a {@link #withCache(Cache) cache} keep their instructions, since
+ * the cache does not record which functions were interpreted.
+ */
+ public Builder withReleasedFunctionBodies() {
+ this.releaseFunctionBodies = true;
+ return this;
+ }
+
public Builder withCache(Cache cache) {
this.cache = cache;
return this;
@@ -152,13 +171,32 @@ public Function compile() {
cache.putIfAbsent(module.digest(), storeClassLoadingCollector(collector));
}
- return new MachineFactory(module, collector.machineFactory());
+ WasmModule compiledModule = module;
+ if (releaseFunctionBodies) {
+ compiledModule = withoutCompiledBodies(module, result.interpretedFunctions());
+ }
+ return new MachineFactory(compiledModule, collector.machineFactory());
} catch (IOException e) {
throw new WasmEngineException(e);
}
}
}
+ private static WasmModule withoutCompiledBodies(WasmModule module, Set interpreted) {
+ int imports = module.importSection().count(ExternalType.FUNCTION);
+ var code = module.codeSection();
+ var stripped = CodeSection.builder().setRequiresDataCount(code.isRequiresDataCount());
+ for (int i = 0; i < code.functionBodyCount(); i++) {
+ FunctionBody body = code.getFunctionBody(i);
+ if (interpreted.contains(imports + i)) {
+ stripped.addFunctionBody(body);
+ } else {
+ stripped.addFunctionBody(new FunctionBody(body.localTypes(), List.of()));
+ }
+ }
+ return module.withCodeSection(stripped.build());
+ }
+
private static byte[] storeClassLoadingCollector(ClassLoadingCollector collector) {
try {
// Create JAR in memory
diff --git a/compiler/src/main/java/run/endive/compiler/internal/CallCycles.java b/compiler/src/main/java/run/endive/compiler/internal/CallCycles.java
new file mode 100644
index 000000000..6e86c8818
--- /dev/null
+++ b/compiler/src/main/java/run/endive/compiler/internal/CallCycles.java
@@ -0,0 +1,141 @@
+package run.endive.compiler.internal;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import run.endive.wasm.WasmModule;
+import run.endive.wasm.types.ExternalType;
+import run.endive.wasm.types.OpCode;
+
+/**
+ * Finds the direct calls that can take part in unbounded execution without a backward branch.
+ *
+ * A guest can only run without end by branching backwards, by calling through a table or a
+ * reference, by calling into the host (which may call back), or by calling around a cycle of the
+ * direct call graph (for example {@code f(n) = f(n - 1) + f(n - 1)}, whose depth stays small).
+ * Direct calls to a function outside every cycle always return after a bounded amount of work
+ * unless they branch backwards, call indirectly or call the host themselves, all of which are
+ * checked, so they need no interruption check of their own.
+ */
+final class CallCycles {
+
+ private CallCycles() {}
+
+ /**
+ * Returns, for each function index (imports first), whether a direct call to it must check
+ * for thread interruption.
+ */
+ static boolean[] checkedCallees(WasmModule module) {
+ int imports = module.importSection().count(ExternalType.FUNCTION);
+ int defined = module.functionSection().functionCount();
+ boolean[] checked = new boolean[imports + defined];
+ Arrays.fill(checked, 0, imports, true);
+
+ List edges = new ArrayList<>(defined);
+ for (int i = 0; i < defined; i++) {
+ edges.add(directCallees(module, i, imports));
+ }
+
+ boolean[] cyclic = cyclicFunctions(edges);
+ for (int i = 0; i < defined; i++) {
+ checked[imports + i] = cyclic[i];
+ }
+ return checked;
+ }
+
+ private static int[] directCallees(WasmModule module, int function, int imports) {
+ var body = module.codeSection().getFunctionBody(function);
+ int[] callees = new int[8];
+ int count = 0;
+ for (var ins : body.instructions()) {
+ if (ins.opcode() != OpCode.CALL && ins.opcode() != OpCode.RETURN_CALL) {
+ continue;
+ }
+ int callee = (int) ins.operand(0) - imports;
+ if (callee < 0) {
+ continue;
+ }
+ if (count == callees.length) {
+ callees = Arrays.copyOf(callees, count * 2);
+ }
+ callees[count++] = callee;
+ }
+ return Arrays.copyOf(callees, count);
+ }
+
+ /** Tarjan's strongly connected components, iterative so deep call graphs cannot overflow. */
+ private static boolean[] cyclicFunctions(List edges) {
+ int count = edges.size();
+ int[] index = new int[count];
+ int[] lowLink = new int[count];
+ boolean[] onStack = new boolean[count];
+ boolean[] cyclic = new boolean[count];
+ Arrays.fill(index, -1);
+
+ int[] stack = new int[count];
+ int stackSize = 0;
+ int[] callStack = new int[count];
+ int[] edgeCursor = new int[count];
+ int nextIndex = 0;
+
+ for (int root = 0; root < count; root++) {
+ if (index[root] != -1) {
+ continue;
+ }
+ int depth = 0;
+ callStack[depth] = root;
+ edgeCursor[root] = 0;
+ index[root] = nextIndex;
+ lowLink[root] = nextIndex;
+ nextIndex++;
+ stack[stackSize++] = root;
+ onStack[root] = true;
+
+ while (depth >= 0) {
+ int node = callStack[depth];
+ int[] callees = edges.get(node);
+ if (edgeCursor[node] < callees.length) {
+ int callee = callees[edgeCursor[node]++];
+ if (callee == node) {
+ cyclic[node] = true;
+ }
+ if (index[callee] == -1) {
+ index[callee] = nextIndex;
+ lowLink[callee] = nextIndex;
+ nextIndex++;
+ stack[stackSize++] = callee;
+ onStack[callee] = true;
+ edgeCursor[callee] = 0;
+ callStack[++depth] = callee;
+ } else if (onStack[callee]) {
+ lowLink[node] = Math.min(lowLink[node], index[callee]);
+ }
+ continue;
+ }
+
+ if (lowLink[node] == index[node]) {
+ int size = 0;
+ int member;
+ int first = stackSize;
+ do {
+ member = stack[--stackSize];
+ onStack[member] = false;
+ size++;
+ } while (member != node);
+ if (size > 1) {
+ for (int i = stackSize; i < first; i++) {
+ cyclic[stack[i]] = true;
+ }
+ }
+ }
+
+ depth--;
+ if (depth >= 0) {
+ int parent = callStack[depth];
+ lowLink[parent] = Math.min(lowLink[parent], lowLink[node]);
+ }
+ }
+ }
+ return cyclic;
+ }
+}
diff --git a/compiler/src/main/java/run/endive/compiler/internal/Compiler.java b/compiler/src/main/java/run/endive/compiler/internal/Compiler.java
index 0d874286d..c42c96d32 100644
--- a/compiler/src/main/java/run/endive/compiler/internal/Compiler.java
+++ b/compiler/src/main/java/run/endive/compiler/internal/Compiler.java
@@ -158,6 +158,7 @@ public final class Compiler {
private int maxFunctionsPerClass;
private final HashSet interpretedFunctions;
private final Set callRefTypeIds;
+ private final boolean[] checkedCallees;
private final boolean[] tailCallFunctions;
private final boolean[] tailCallTypes;
private final boolean moduleHasTailCalls;
@@ -198,6 +199,7 @@ private Compiler(
this.functionTypes = analyzer.functionTypes();
this.callRefTypeIds = collectCallRefTypeIds();
+ this.checkedCallees = CallCycles.checkedCallees(module);
this.tailCallFunctions = analyzer.tailCallFunctions();
this.tailCallTypes = analyzer.tailCallTypes();
this.moduleHasTailCalls = analyzer.hasTailCalls();
@@ -2147,6 +2149,7 @@ private void compileFunction(
body,
tailCallFunctions,
tailCallTypes,
+ checkedCallees,
useBridgeClasses ? callIndirectClassResolver : typeId -> internalClassName,
analysis.maxTempSlots(),
this::methodName);
diff --git a/compiler/src/main/java/run/endive/compiler/internal/Context.java b/compiler/src/main/java/run/endive/compiler/internal/Context.java
index bdbd6b09f..d7dc2f69e 100644
--- a/compiler/src/main/java/run/endive/compiler/internal/Context.java
+++ b/compiler/src/main/java/run/endive/compiler/internal/Context.java
@@ -27,6 +27,7 @@ final class Context {
private final FunctionBody body;
private final boolean[] tailCallFunctions;
private final boolean[] tailCallTypes;
+ private final boolean[] checkedCallees;
private final List slots;
private final int memorySlot;
private final int instanceSlot;
@@ -46,6 +47,7 @@ public Context(
FunctionBody body,
boolean[] tailCallFunctions,
boolean[] tailCallTypes,
+ boolean[] checkedCallees,
IntFunction callIndirectClassResolver,
int maxTempSlots,
IntFunction methodNames) {
@@ -59,6 +61,7 @@ public Context(
this.body = body;
this.tailCallFunctions = tailCallFunctions;
this.tailCallTypes = tailCallTypes;
+ this.checkedCallees = checkedCallees;
this.callIndirectClassResolver = callIndirectClassResolver;
this.methodNames = methodNames;
@@ -184,4 +187,8 @@ public FunctionType tagFunctionType(int tagId) {
}
return type;
}
+
+ public boolean checksInterruptionOnCall(int funcId) {
+ return checkedCallees[funcId];
+ }
}
diff --git a/compiler/src/main/java/run/endive/compiler/internal/Emitters.java b/compiler/src/main/java/run/endive/compiler/internal/Emitters.java
index c4e767ad0..d05a81aa7 100644
--- a/compiler/src/main/java/run/endive/compiler/internal/Emitters.java
+++ b/compiler/src/main/java/run/endive/compiler/internal/Emitters.java
@@ -387,7 +387,9 @@ public static void CALL(Context ctx, CompilerInstruction ins, InstructionAdapter
int funcId = (int) ins.operand(0);
FunctionType functionType = ctx.functionTypes().get(funcId);
- emitInvokeStatic(asm, ShadedRefs.CHECK_INTERRUPTION);
+ if (ctx.checksInterruptionOnCall(funcId)) {
+ emitInvokeStatic(asm, ShadedRefs.CHECK_INTERRUPTION);
+ }
if (hasTooManyParameters(functionType)) {
emitBoxValuesOnStack(ctx, asm, functionType.params());
}
diff --git a/compiler/src/test/java/run/endive/compiler/internal/ReleasedFunctionBodiesTest.java b/compiler/src/test/java/run/endive/compiler/internal/ReleasedFunctionBodiesTest.java
new file mode 100644
index 000000000..b8308ddaf
--- /dev/null
+++ b/compiler/src/test/java/run/endive/compiler/internal/ReleasedFunctionBodiesTest.java
@@ -0,0 +1,60 @@
+package run.endive.compiler.internal;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Set;
+import org.junit.jupiter.api.Test;
+import run.endive.compiler.InterpreterFallback;
+import run.endive.compiler.MachineFactoryCompiler;
+import run.endive.corpus.CorpusResources;
+import run.endive.runtime.CompiledModule;
+import run.endive.runtime.Instance;
+import run.endive.wasm.Parser;
+import run.endive.wasm.WasmModule;
+import run.endive.wasm.types.ExternalType;
+
+public class ReleasedFunctionBodiesTest {
+
+ @Test
+ public void compiledFunctionsRunWithoutTheirInstructions() {
+ WasmModule module = Parser.parse(CorpusResources.getResource("compiled/power.c.wasm"));
+ var factory = MachineFactoryCompiler.builder(module).withReleasedFunctionBodies().compile();
+ WasmModule released = ((CompiledModule) factory).wasmModule();
+
+ for (var body : released.codeSection().functionBodies()) {
+ assertTrue(body.instructions().isEmpty());
+ }
+ var instance = Instance.builder(released).withMachineFactory(factory).build();
+ assertEquals(16, instance.export("run").apply(4)[0]);
+ }
+
+ @Test
+ public void interpretedFunctionsKeepTheirInstructions() {
+ WasmModule module = Parser.parse(CorpusResources.getResource("compiled/power.c.wasm"));
+ int run = exportedFunction(module, "run");
+ int imports = module.importSection().count(ExternalType.FUNCTION);
+ var factory =
+ MachineFactoryCompiler.builder(module)
+ .withInterpreterFallback(InterpreterFallback.FAIL)
+ .withInterpretedFunctions(Set.of(run))
+ .withReleasedFunctionBodies()
+ .compile();
+ WasmModule released = ((CompiledModule) factory).wasmModule();
+
+ assertFalse(released.codeSection().getFunctionBody(run - imports).instructions().isEmpty());
+ var instance = Instance.builder(released).withMachineFactory(factory).build();
+ assertEquals(16, instance.export("run").apply(4)[0]);
+ }
+
+ private static int exportedFunction(WasmModule module, String name) {
+ var exports = module.exportSection();
+ for (int i = 0; i < exports.exportCount(); i++) {
+ if (exports.getExport(i).name().equals(name)) {
+ return (int) exports.getExport(i).index();
+ }
+ }
+ throw new IllegalArgumentException("no export " + name);
+ }
+}
diff --git a/dircache/pom.xml b/dircache/pom.xml
index af43833e0..eb0173b6d 100644
--- a/dircache/pom.xml
+++ b/dircache/pom.xml
@@ -5,7 +5,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
dircache-experimental
jar
diff --git a/docs-lib/pom.xml b/docs-lib/pom.xml
index 62220f060..a44f1ede6 100644
--- a/docs-lib/pom.xml
+++ b/docs-lib/pom.xml
@@ -5,7 +5,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
../pom.xml
docs-lib
diff --git a/fuzz/pom.xml b/fuzz/pom.xml
index a306e665d..f57279e27 100644
--- a/fuzz/pom.xml
+++ b/fuzz/pom.xml
@@ -5,7 +5,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
fuzz
jar
diff --git a/jmh/pom.xml b/jmh/pom.xml
index 32a7d3806..d03fcf21d 100644
--- a/jmh/pom.xml
+++ b/jmh/pom.xml
@@ -5,7 +5,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
../pom.xml
jmh
diff --git a/log/pom.xml b/log/pom.xml
index be6a21a27..bc3db08eb 100644
--- a/log/pom.xml
+++ b/log/pom.xml
@@ -5,7 +5,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
log
jar
diff --git a/machine-tests/pom.xml b/machine-tests/pom.xml
index c8570d7d6..5fdfeec13 100644
--- a/machine-tests/pom.xml
+++ b/machine-tests/pom.xml
@@ -5,7 +5,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
machine-tests
jar
diff --git a/pom.xml b/pom.xml
index f442ad071..fb9199e13 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
pom
Endive
Native JVM WebAssembly runtime
diff --git a/redline/api/pom.xml b/redline/api/pom.xml
index 3b0717413..a769838f2 100644
--- a/redline/api/pom.xml
+++ b/redline/api/pom.xml
@@ -5,7 +5,7 @@
run.endive
redline-parent-experimental
- 999-SNAPSHOT
+ 1.1.0-sculk.3
../pom.xml
redline-api-experimental
diff --git a/redline/bridge/pom.xml b/redline/bridge/pom.xml
index 2381e3efa..ee3b52394 100644
--- a/redline/bridge/pom.xml
+++ b/redline/bridge/pom.xml
@@ -5,7 +5,7 @@
run.endive
redline-parent-experimental
- 999-SNAPSHOT
+ 1.1.0-sculk.3
../pom.xml
redline-bridge-experimental
diff --git a/redline/build-time-compiler/pom.xml b/redline/build-time-compiler/pom.xml
index 50ee68428..2e185ea1c 100644
--- a/redline/build-time-compiler/pom.xml
+++ b/redline/build-time-compiler/pom.xml
@@ -5,7 +5,7 @@
run.endive
redline-parent-experimental
- 999-SNAPSHOT
+ 1.1.0-sculk.3
../pom.xml
redline-build-time-compiler-experimental
diff --git a/redline/compiler/pom.xml b/redline/compiler/pom.xml
index 0106e935a..1ccd7e28b 100644
--- a/redline/compiler/pom.xml
+++ b/redline/compiler/pom.xml
@@ -5,7 +5,7 @@
run.endive
redline-parent-experimental
- 999-SNAPSHOT
+ 1.1.0-sculk.3
../pom.xml
redline-compiler-experimental
diff --git a/redline/it/pom.xml b/redline/it/pom.xml
index 42e7fb2ee..6070dbeea 100644
--- a/redline/it/pom.xml
+++ b/redline/it/pom.xml
@@ -5,7 +5,7 @@
run.endive
redline-parent-experimental
- 999-SNAPSHOT
+ 1.1.0-sculk.3
../pom.xml
redline-it-experimental
diff --git a/redline/pom.xml b/redline/pom.xml
index 772ef4d93..fb02218b2 100644
--- a/redline/pom.xml
+++ b/redline/pom.xml
@@ -5,7 +5,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
../pom.xml
redline-parent-experimental
diff --git a/redline/runner-jffi-tests/pom.xml b/redline/runner-jffi-tests/pom.xml
index e84f52570..eb043dce0 100644
--- a/redline/runner-jffi-tests/pom.xml
+++ b/redline/runner-jffi-tests/pom.xml
@@ -5,7 +5,7 @@
run.endive
redline-parent-experimental
- 999-SNAPSHOT
+ 1.1.0-sculk.3
../pom.xml
redline-runner-jffi-tests-experimental
diff --git a/redline/runner-jffi/pom.xml b/redline/runner-jffi/pom.xml
index 17917a57c..a21530fe4 100644
--- a/redline/runner-jffi/pom.xml
+++ b/redline/runner-jffi/pom.xml
@@ -5,7 +5,7 @@
run.endive
redline-parent-experimental
- 999-SNAPSHOT
+ 1.1.0-sculk.3
../pom.xml
redline-runner-jffi-experimental
diff --git a/redline/runner-tests/pom.xml b/redline/runner-tests/pom.xml
index 95a6b06be..1f44ac017 100644
--- a/redline/runner-tests/pom.xml
+++ b/redline/runner-tests/pom.xml
@@ -5,7 +5,7 @@
run.endive
redline-parent-experimental
- 999-SNAPSHOT
+ 1.1.0-sculk.3
../pom.xml
redline-runner-tests-experimental
diff --git a/redline/runner/pom.xml b/redline/runner/pom.xml
index 420368929..54ebae02f 100644
--- a/redline/runner/pom.xml
+++ b/redline/runner/pom.xml
@@ -5,7 +5,7 @@
run.endive
redline-parent-experimental
- 999-SNAPSHOT
+ 1.1.0-sculk.3
../pom.xml
redline-runner-experimental
diff --git a/runtime-tests/pom.xml b/runtime-tests/pom.xml
index ebe2e10c3..3ca1950aa 100644
--- a/runtime-tests/pom.xml
+++ b/runtime-tests/pom.xml
@@ -5,7 +5,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
../pom.xml
runtime-tests
diff --git a/runtime/pom.xml b/runtime/pom.xml
index bd36b5ccb..c0878ba70 100644
--- a/runtime/pom.xml
+++ b/runtime/pom.xml
@@ -5,7 +5,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
../pom.xml
runtime
diff --git a/runtime/src/main/java/run/endive/runtime/FlatByteArrayMemory.java b/runtime/src/main/java/run/endive/runtime/FlatByteArrayMemory.java
new file mode 100644
index 000000000..71f056655
--- /dev/null
+++ b/runtime/src/main/java/run/endive/runtime/FlatByteArrayMemory.java
@@ -0,0 +1,345 @@
+package run.endive.runtime;
+
+import static java.lang.Math.min;
+import static run.endive.runtime.ConstantEvaluators.computeConstantValue;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+import run.endive.wasm.UninstantiableException;
+import run.endive.wasm.WasmEngineException;
+import run.endive.wasm.types.ActiveDataSegment;
+import run.endive.wasm.types.DataSegment;
+import run.endive.wasm.types.MemoryLimits;
+import run.endive.wasm.types.PassiveDataSegment;
+
+/**
+ * Linear memory kept in one contiguous byte array, for memories that are not shared.
+ *
+ * Every load and store is a single bounds-checked access to that array: there is no page to
+ * look up and no access that can straddle two pages. Growing the memory copies it into a larger
+ * array, which is why shared memories, whose other threads could still be reading the old array,
+ * are rejected: use {@link ByteArrayMemory} for those.
+ *
+ *
The memory can grow to {@link Memory#RUNTIME_MAX_PAGES} pages, the largest size a single
+ * Java array can hold.
+ */
+public final class FlatByteArrayMemory implements Memory {
+ // Same note as ByteArrayMemory: catching RuntimeException keeps the accessors short enough to
+ // inline, and the JVM's own array bounds check is the wasm bounds check.
+
+ private static final VarHandle SHORT_ARR_HANDLE =
+ MethodHandles.byteArrayViewVarHandle(short[].class, ByteOrder.LITTLE_ENDIAN);
+ private static final VarHandle INT_ARR_HANDLE =
+ MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.LITTLE_ENDIAN);
+ private static final VarHandle FLOAT_ARR_HANDLE =
+ MethodHandles.byteArrayViewVarHandle(float[].class, ByteOrder.LITTLE_ENDIAN);
+ private static final VarHandle LONG_ARR_HANDLE =
+ MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.LITTLE_ENDIAN);
+ private static final VarHandle DOUBLE_ARR_HANDLE =
+ MethodHandles.byteArrayViewVarHandle(double[].class, ByteOrder.LITTLE_ENDIAN);
+
+ private final MemoryLimits limits;
+ private DataSegment[] dataSegments;
+ private byte[] buffer;
+
+ public FlatByteArrayMemory(MemoryLimits limits) {
+ if (limits.shared()) {
+ throw new IllegalArgumentException(
+ "FlatByteArrayMemory cannot back a shared memory; use ByteArrayMemory");
+ }
+ this.limits = limits;
+ this.buffer = new byte[PAGE_SIZE * min(limits.initialPages(), maximumPages())];
+ }
+
+ @Override
+ public int pages() {
+ return buffer.length / PAGE_SIZE;
+ }
+
+ @Override
+ public int grow(int size) {
+ int prevPages = pages();
+ int numPages = prevPages + size;
+ if (numPages > maximumPages() || numPages < prevPages) {
+ return -1;
+ }
+ if (numPages != prevPages) {
+ buffer = Arrays.copyOf(buffer, numPages * PAGE_SIZE);
+ }
+ return prevPages;
+ }
+
+ @Override
+ public int initialPages() {
+ return limits.initialPages();
+ }
+
+ @Override
+ public int maximumPages() {
+ return min(limits.maximumPages(), RUNTIME_MAX_PAGES);
+ }
+
+ @Override
+ public boolean shared() {
+ return false;
+ }
+
+ @Override
+ @SuppressWarnings("removal")
+ public Object lock(int address) {
+ return this;
+ }
+
+ @Override
+ @SuppressWarnings("removal")
+ public int waitOn(int address, int expected, long timeout) {
+ throw new WasmEngineException("Attempt to wait on a non-shared memory, not supported.");
+ }
+
+ @Override
+ @SuppressWarnings("removal")
+ public int waitOn(int address, long expected, long timeout) {
+ throw new WasmEngineException("Attempt to wait on a non-shared memory, not supported.");
+ }
+
+ @Override
+ @SuppressWarnings("removal")
+ public int notify(int address, int maxThreads) {
+ return 0;
+ }
+
+ @Override
+ public void initialize(Instance instance, DataSegment[] dataSegments) {
+ initialize(instance, dataSegments, 0);
+ }
+
+ @Override
+ public void initialize(Instance instance, DataSegment[] dataSegments, int memoryIndex) {
+ this.dataSegments = dataSegments;
+ if (dataSegments == null) {
+ return;
+ }
+ for (var s : dataSegments) {
+ if (s instanceof ActiveDataSegment) {
+ var segment = (ActiveDataSegment) s;
+ if (segment.index() != memoryIndex) {
+ continue;
+ }
+ var data = segment.bytes();
+ var offset = (int) computeConstantValue(instance, segment.offsetInstructions())[0];
+ if (outOfBounds(offset, data.length, buffer.length)) {
+ throw new UninstantiableException(boundsMessage(offset, data.length));
+ }
+ write(offset, data, 0, data.length);
+ } else if (!(s instanceof PassiveDataSegment)) {
+ throw new WasmEngineException("Data segment should be active or passive: " + s);
+ }
+ }
+ }
+
+ @Override
+ public void initPassiveSegment(int segmentId, int dest, int offset, int size) {
+ write(dest, dataSegments[segmentId].bytes(), offset, size);
+ }
+
+ @Override
+ public void drop(int segment) {
+ dataSegments[segment] = PassiveDataSegment.EMPTY;
+ }
+
+ private static boolean outOfBounds(int addr, int size, int limit) {
+ return addr < 0
+ || size < 0
+ || addr > limit
+ || (size > 0 && (long) addr + (long) size > (long) limit);
+ }
+
+ private String boundsMessage(int addr, int size) {
+ return "out of bounds memory access: attempted to access address: "
+ + addr
+ + " but limit is: "
+ + buffer.length
+ + " and size: "
+ + size;
+ }
+
+ private RuntimeException outOfBoundsException(RuntimeException e, int addr, int size) {
+ if (e instanceof IndexOutOfBoundsException
+ || e instanceof IllegalArgumentException
+ || e instanceof NegativeArraySizeException) {
+ return new WasmRuntimeException(boundsMessage(addr, size));
+ }
+ return e;
+ }
+
+ @Override
+ public void write(int addr, byte[] data, int offset, int size) {
+ if (outOfBounds(offset, size, data.length)) {
+ throw new WasmRuntimeException(
+ "out of bounds memory access: attempted to read "
+ + size
+ + " bytes at "
+ + offset
+ + " from an array of "
+ + data.length);
+ }
+ if (outOfBounds(addr, size, buffer.length)) {
+ throw new WasmRuntimeException(boundsMessage(addr, size));
+ }
+ System.arraycopy(data, offset, buffer, addr, size);
+ }
+
+ @Override
+ public byte read(int addr) {
+ try {
+ return buffer[addr];
+ } catch (RuntimeException e) {
+ throw outOfBoundsException(e, addr, 1);
+ }
+ }
+
+ @Override
+ public byte[] readBytes(int addr, int len) {
+ if (outOfBounds(addr, len, buffer.length)) {
+ throw new WasmRuntimeException(boundsMessage(addr, len));
+ }
+ return Arrays.copyOfRange(buffer, addr, addr + len);
+ }
+
+ @Override
+ public void writeI32(int addr, int data) {
+ try {
+ INT_ARR_HANDLE.set(buffer, addr, data);
+ } catch (RuntimeException e) {
+ throw outOfBoundsException(e, addr, 4);
+ }
+ }
+
+ @Override
+ public int readInt(int addr) {
+ try {
+ return (int) INT_ARR_HANDLE.get(buffer, addr);
+ } catch (RuntimeException e) {
+ throw outOfBoundsException(e, addr, 4);
+ }
+ }
+
+ @Override
+ public void writeLong(int addr, long data) {
+ try {
+ LONG_ARR_HANDLE.set(buffer, addr, data);
+ } catch (RuntimeException e) {
+ throw outOfBoundsException(e, addr, 8);
+ }
+ }
+
+ @Override
+ public long readLong(int addr) {
+ try {
+ return (long) LONG_ARR_HANDLE.get(buffer, addr);
+ } catch (RuntimeException e) {
+ throw outOfBoundsException(e, addr, 8);
+ }
+ }
+
+ @Override
+ public void writeShort(int addr, short data) {
+ try {
+ SHORT_ARR_HANDLE.set(buffer, addr, data);
+ } catch (RuntimeException e) {
+ throw outOfBoundsException(e, addr, 2);
+ }
+ }
+
+ @Override
+ public short readShort(int addr) {
+ try {
+ return (short) SHORT_ARR_HANDLE.get(buffer, addr);
+ } catch (RuntimeException e) {
+ throw outOfBoundsException(e, addr, 2);
+ }
+ }
+
+ @Override
+ public long readU16(int addr) {
+ return readShort(addr) & 0xFFFF;
+ }
+
+ @Override
+ public void writeByte(int addr, byte data) {
+ try {
+ buffer[addr] = data;
+ } catch (RuntimeException e) {
+ throw outOfBoundsException(e, addr, 1);
+ }
+ }
+
+ @Override
+ public void writeF32(int addr, float data) {
+ try {
+ FLOAT_ARR_HANDLE.set(buffer, addr, data);
+ } catch (RuntimeException e) {
+ throw outOfBoundsException(e, addr, 4);
+ }
+ }
+
+ @Override
+ public long readF32(int addr) {
+ return readInt(addr);
+ }
+
+ @Override
+ public float readFloat(int addr) {
+ try {
+ return (float) FLOAT_ARR_HANDLE.get(buffer, addr);
+ } catch (RuntimeException e) {
+ throw outOfBoundsException(e, addr, 4);
+ }
+ }
+
+ @Override
+ public void writeF64(int addr, double data) {
+ try {
+ DOUBLE_ARR_HANDLE.set(buffer, addr, data);
+ } catch (RuntimeException e) {
+ throw outOfBoundsException(e, addr, 8);
+ }
+ }
+
+ @Override
+ public double readDouble(int addr) {
+ try {
+ return (double) DOUBLE_ARR_HANDLE.get(buffer, addr);
+ } catch (RuntimeException e) {
+ throw outOfBoundsException(e, addr, 8);
+ }
+ }
+
+ @Override
+ public long readF64(int addr) {
+ return readLong(addr);
+ }
+
+ @Override
+ public void zero() {
+ Arrays.fill(buffer, (byte) 0);
+ }
+
+ @Override
+ public void fill(byte value, int fromIndex, int toIndex) {
+ if (outOfBounds(fromIndex, toIndex - fromIndex, buffer.length)) {
+ throw new WasmRuntimeException(boundsMessage(fromIndex, toIndex - fromIndex));
+ }
+ Arrays.fill(buffer, fromIndex, toIndex, value);
+ }
+
+ @Override
+ public void copy(int dest, int src, int size) {
+ if (outOfBounds(src, size, buffer.length) || outOfBounds(dest, size, buffer.length)) {
+ throw new WasmRuntimeException(boundsMessage(Math.max(src, dest), size));
+ }
+ System.arraycopy(buffer, src, buffer, dest, size);
+ }
+}
diff --git a/runtime/src/main/java/run/endive/runtime/Instance.java b/runtime/src/main/java/run/endive/runtime/Instance.java
index 2a582a3f2..45f73b7ac 100644
--- a/runtime/src/main/java/run/endive/runtime/Instance.java
+++ b/runtime/src/main/java/run/endive/runtime/Instance.java
@@ -62,6 +62,8 @@ public class Instance implements AutoCloseable {
private final DataSegment[] dataSegments;
private final Global[] globalInitializers;
private final GlobalInstance[] globals;
+ // imported globals first, then the module's own: the index space of global.get and global.set
+ private final GlobalInstance[] globalSpace;
private final FunctionType[] types;
private final int[] functionTypes;
private final ImportValues imports;
@@ -120,6 +122,10 @@ static final class TailCallPending {
this.types = types.clone();
this.functionTypes = functionTypes.clone();
this.imports = imports;
+ this.globalSpace = new GlobalInstance[imports.globalCount() + globalInitializers.length];
+ for (int i = 0; i < imports.globalCount(); i++) {
+ globalSpace[i] = imports.global(i).instance();
+ }
this.machine = machineFactory.apply(this);
this.tables = new TableInstance[tables.length];
this.elements = elements.clone();
@@ -178,6 +184,7 @@ public Instance initialize(boolean start) {
.build();
}
globals[i].setInstance(this);
+ globalSpace[imports.globalCount() + i] = globals[i];
if (g.valueType().isReference()) {
globals[i].setRefValue(result.ref());
}
@@ -387,14 +394,10 @@ public void dropDataSegment(int idx) {
}
public GlobalInstance global(int idx) {
- if (idx < imports.globalCount()) {
- return imports.global(idx).instance();
- }
- var i = idx - imports.globalCount();
- if (i < 0 || i >= globals.length) {
+ if (idx < 0 || idx >= globalSpace.length) {
throw new InvalidException("unknown global " + idx);
}
- return globals[idx - imports.globalCount()];
+ return globalSpace[idx];
}
public FunctionType type(int idx) {
diff --git a/runtime/src/test/java/run/endive/runtime/MemoryTest.java b/runtime/src/test/java/run/endive/runtime/MemoryTest.java
index 2bf58e464..484d42ee4 100644
--- a/runtime/src/test/java/run/endive/runtime/MemoryTest.java
+++ b/runtime/src/test/java/run/endive/runtime/MemoryTest.java
@@ -24,7 +24,10 @@ private static Stream memoryImplementations() {
(Supplier) () -> new ByteArrayMemory(new MemoryLimits(2, 2))),
Arguments.of(
"ByteBufferMemory",
- (Supplier) () -> new ByteBufferMemory(new MemoryLimits(2, 2))));
+ (Supplier) () -> new ByteBufferMemory(new MemoryLimits(2, 2))),
+ Arguments.of(
+ "FlatByteArrayMemory",
+ (Supplier) () -> new FlatByteArrayMemory(new MemoryLimits(2, 2))));
}
private static Stream growableMemoryImplementations() {
diff --git a/simd/pom.xml b/simd/pom.xml
index f63818d90..17d08b1d1 100644
--- a/simd/pom.xml
+++ b/simd/pom.xml
@@ -5,7 +5,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
../pom.xml
simd
diff --git a/test-gen-lib/pom.xml b/test-gen-lib/pom.xml
index ea1f10813..21490de51 100644
--- a/test-gen-lib/pom.xml
+++ b/test-gen-lib/pom.xml
@@ -5,7 +5,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
../pom.xml
test-gen-lib
diff --git a/test-gen-plugin/pom.xml b/test-gen-plugin/pom.xml
index f4269fa02..89254ff00 100644
--- a/test-gen-plugin/pom.xml
+++ b/test-gen-plugin/pom.xml
@@ -5,7 +5,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
../pom.xml
test-gen-plugin
diff --git a/wabt/pom.xml b/wabt/pom.xml
index ddd1f18d6..cc42971c6 100644
--- a/wabt/pom.xml
+++ b/wabt/pom.xml
@@ -5,7 +5,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
wabt
jar
diff --git a/wasi-test-gen-plugin/pom.xml b/wasi-test-gen-plugin/pom.xml
index b9236f5b1..582dc841b 100644
--- a/wasi-test-gen-plugin/pom.xml
+++ b/wasi-test-gen-plugin/pom.xml
@@ -5,7 +5,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
../pom.xml
wasi-test-gen-plugin
diff --git a/wasi-tests/pom.xml b/wasi-tests/pom.xml
index ed3814a8e..6737413b0 100644
--- a/wasi-tests/pom.xml
+++ b/wasi-tests/pom.xml
@@ -5,7 +5,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
../pom.xml
wasi-tests
diff --git a/wasi/pom.xml b/wasi/pom.xml
index 04942e9b9..afac1597e 100644
--- a/wasi/pom.xml
+++ b/wasi/pom.xml
@@ -5,7 +5,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
../pom.xml
wasi
diff --git a/wasm-corpus/pom.xml b/wasm-corpus/pom.xml
index fa1250757..023fd9f8f 100644
--- a/wasm-corpus/pom.xml
+++ b/wasm-corpus/pom.xml
@@ -4,7 +4,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
wasm-corpus
Endive - WASM Corpus
diff --git a/wasm-tools/pom.xml b/wasm-tools/pom.xml
index 553588fdd..b8fc11dab 100644
--- a/wasm-tools/pom.xml
+++ b/wasm-tools/pom.xml
@@ -5,7 +5,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
wasm-tools
jar
diff --git a/wasm/pom.xml b/wasm/pom.xml
index b674ec5df..88f996886 100644
--- a/wasm/pom.xml
+++ b/wasm/pom.xml
@@ -5,7 +5,7 @@
run.endive
endive
- 999-SNAPSHOT
+ 1.1.0-sculk.3
../pom.xml
wasm
diff --git a/wasm/src/main/java/run/endive/wasm/WasmModule.java b/wasm/src/main/java/run/endive/wasm/WasmModule.java
index ed732b320..60bacabbc 100644
--- a/wasm/src/main/java/run/endive/wasm/WasmModule.java
+++ b/wasm/src/main/java/run/endive/wasm/WasmModule.java
@@ -126,6 +126,30 @@ public String digest() {
return digest;
}
+ /**
+ * Returns a copy of this module with the given code section in place of its own. Every other
+ * section, the custom sections and the digest are shared with this module.
+ */
+ public WasmModule withCodeSection(CodeSection replacement) {
+ return new WasmModule(
+ typeSection,
+ importSection,
+ functionSection,
+ tableSection,
+ memorySection,
+ globalSection,
+ exportSection,
+ startSection,
+ elementSection,
+ replacement,
+ dataSection,
+ dataCountSection,
+ tagSection,
+ customSections,
+ ignoredSections,
+ digest);
+ }
+
public List customSections() {
return new ArrayList<>(customSections.values());
}