Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion annotations/annotations/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>run.endive</groupId>
<artifactId>endive</artifactId>
<version>999-SNAPSHOT</version>
<version>1.1.0-sculk.3</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>annotations</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion annotations/it/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>run.endive</groupId>
<artifactId>endive</artifactId>
<version>999-SNAPSHOT</version>
<version>1.1.0-sculk.3</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>annotations-it</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion annotations/processor/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>run.endive</groupId>
<artifactId>endive</artifactId>
<version>999-SNAPSHOT</version>
<version>1.1.0-sculk.3</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>annotations-processor</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>run.endive</groupId>
<artifactId>endive</artifactId>
<version>999-SNAPSHOT</version>
<version>1.1.0-sculk.3</version>
</parent>
<artifactId>bom</artifactId>
<packaging>pom</packaging>
Expand Down
2 changes: 1 addition & 1 deletion build-time-compiler-cli/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>run.endive</groupId>
<artifactId>endive</artifactId>
<version>999-SNAPSHOT</version>
<version>1.1.0-sculk.3</version>
</parent>
<artifactId>build-time-compiler-cli-experimental</artifactId>
<packaging>jar</packaging>
Expand Down
2 changes: 1 addition & 1 deletion build-time-compiler/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>run.endive</groupId>
<artifactId>endive</artifactId>
<version>999-SNAPSHOT</version>
<version>1.1.0-sculk.3</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>build-time-compiler</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion cli/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>run.endive</groupId>
<artifactId>endive</artifactId>
<version>999-SNAPSHOT</version>
<version>1.1.0-sculk.3</version>
</parent>
<artifactId>cli-experimental</artifactId>
<packaging>jar</packaging>
Expand Down
2 changes: 1 addition & 1 deletion codegen/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>run.endive</groupId>
<artifactId>endive</artifactId>
<version>999-SNAPSHOT</version>
<version>1.1.0-sculk.3</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>codegen</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion compiler-maven-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>run.endive</groupId>
<artifactId>endive</artifactId>
<version>999-SNAPSHOT</version>
<version>1.1.0-sculk.3</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>endive-compiler-maven-plugin</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion compiler-tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>run.endive</groupId>
<artifactId>endive</artifactId>
<version>999-SNAPSHOT</version>
<version>1.1.0-sculk.3</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>compiler-tests</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion compiler/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>run.endive</groupId>
<artifactId>endive</artifactId>
<version>999-SNAPSHOT</version>
<version>1.1.0-sculk.3</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>compiler</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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.
*
* <p>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;
Expand Down Expand Up @@ -152,13 +171,32 @@ public Function<Instance, Machine> 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<Integer> 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
Expand Down
141 changes: 141 additions & 0 deletions compiler/src/main/java/run/endive/compiler/internal/CallCycles.java
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>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<int[]> 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<int[]> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ public final class Compiler {
private int maxFunctionsPerClass;
private final HashSet<Integer> interpretedFunctions;
private final Set<Integer> callRefTypeIds;
private final boolean[] checkedCallees;
private final boolean[] tailCallFunctions;
private final boolean[] tailCallTypes;
private final boolean moduleHasTailCalls;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -2147,6 +2149,7 @@ private void compileFunction(
body,
tailCallFunctions,
tailCallTypes,
checkedCallees,
useBridgeClasses ? callIndirectClassResolver : typeId -> internalClassName,
analysis.maxTempSlots(),
this::methodName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Integer> slots;
private final int memorySlot;
private final int instanceSlot;
Expand All @@ -46,6 +47,7 @@ public Context(
FunctionBody body,
boolean[] tailCallFunctions,
boolean[] tailCallTypes,
boolean[] checkedCallees,
IntFunction<String> callIndirectClassResolver,
int maxTempSlots,
IntFunction<String> methodNames) {
Expand All @@ -59,6 +61,7 @@ public Context(
this.body = body;
this.tailCallFunctions = tailCallFunctions;
this.tailCallTypes = tailCallTypes;
this.checkedCallees = checkedCallees;
this.callIndirectClassResolver = callIndirectClassResolver;
this.methodNames = methodNames;

Expand Down Expand Up @@ -184,4 +187,8 @@ public FunctionType tagFunctionType(int tagId) {
}
return type;
}

public boolean checksInterruptionOnCall(int funcId) {
return checkedCallees[funcId];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down
Loading
Loading