Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,22 @@
import run.endive.codegen.ModuleInterfaceCodegen;
import run.endive.compiler.internal.ByteClassCollector;
import run.endive.compiler.internal.Compiler;
import run.endive.runtime.ByteBufferMemory;
import run.endive.runtime.CompiledModule;
import run.endive.runtime.Instance;
import run.endive.runtime.Machine;
import run.endive.runtime.Memory;
import run.endive.runtime.TableInstance;
import run.endive.wasm.MalformedException;
import run.endive.wasm.Parser;
import run.endive.wasm.WasmModule;
import run.endive.wasm.WasmWriter;
import run.endive.wasm.types.ExternalType;
import run.endive.wasm.types.MemoryLimits;
import run.endive.wasm.types.OpCode;
import run.endive.wasm.types.RawSection;
import run.endive.wasm.types.SectionId;
import run.endive.wasm.types.Table;

public class Generator {

Expand Down Expand Up @@ -110,6 +115,9 @@ public void generateSources() throws IOException {
generateLoadMethod(cu, type);
generateMachineFactoryMethod(cu, type, moduleName);
generateWasmModuleMethod(cu, type, moduleName);
generateBuilderMethod(cu, type, moduleName);
generateSafeBuilderMethod(cu, type, moduleName);
generateImportFactoryMethods(cu, type);

dest.add(packageName, moduleName + ".java", cu);
dest.saveAll();
Expand Down Expand Up @@ -233,6 +241,105 @@ private static void generateCreateMethod(
method.addStatement(new ReturnStmt(constructorInvocation));
}

/**
* Generates:
* <code>
* public static Instance.Builder builder() {
* return Instance.builder(load()).withMachineFactory(&lt;moduleName&gt;::create);
* }
* </code>
*
* <p>Every generated module has this, so the same user code compiles whether or
* not a backend that replaces it is in play. Redline overwrites the body to pick
* native code when it can, and leaves this one as the fallback.
*/
private static void generateBuilderMethod(
CompilationUnit cu, ClassOrInterfaceDeclaration type, String moduleName) {
cu.addImport(Instance.class);
type.addMethod("builder", Modifier.Keyword.PUBLIC, Modifier.Keyword.STATIC)
.setType(parseClassOrInterfaceType("Instance.Builder"))
.createBody()
.addStatement(
new ReturnStmt(compiledBuilder(new MethodCallExpr("load"), moduleName)));
}

/**
* Generates the same builder under a name no backend replaces, so there is always
* a way to ask for the bytecode specifically.
*/
private static void generateSafeBuilderMethod(
CompilationUnit cu, ClassOrInterfaceDeclaration type, String moduleName) {
cu.addImport(Instance.class);
type.addMethod("safeBuilder", Modifier.Keyword.PUBLIC, Modifier.Keyword.STATIC)
.setType(parseClassOrInterfaceType("Instance.Builder"))
.createBody()
.addStatement(
new ReturnStmt(compiledBuilder(new MethodCallExpr("load"), moduleName)));
}

/** {@code Instance.builder(<module>).withMachineFactory(<moduleName>::create)} */
private static MethodCallExpr compiledBuilder(
com.github.javaparser.ast.expr.Expression module, String moduleName) {
return new MethodCallExpr(
new MethodCallExpr(new NameExpr("Instance"), "builder", new NodeList<>(module)),
"withMachineFactory",
new NodeList<>(
new MethodReferenceExpr()
.setScope(new NameExpr(moduleName))
.setIdentifier("create")));
}

/**
* Generates:
* <code>
* public static Memory createMemory(MemoryLimits limits) {
* return new ByteBufferMemory(limits);
* }
*
* public static TableInstance createTable(Table table, int initValue) {
* return new TableInstance(table, initValue);
* }
* </code>
*
* <p>A backend whose compiled code reaches into a memory or table directly
* cannot accept one built any other way, so it replaces these. Going through
* the module rather than naming a type keeps the calling code the same either
* way.
*/
private static void generateImportFactoryMethods(
CompilationUnit cu, ClassOrInterfaceDeclaration type) {
cu.addImport(Memory.class);
cu.addImport(ByteBufferMemory.class);
cu.addImport(TableInstance.class);
cu.addImport(MemoryLimits.class);
cu.addImport(Table.class);

type.addMethod("createMemory", Modifier.Keyword.PUBLIC, Modifier.Keyword.STATIC)
.addParameter(parseType("MemoryLimits"), "limits")
.setType(Memory.class)
.createBody()
.addStatement(
new ReturnStmt(
new ObjectCreationExpr(
null,
parseClassOrInterfaceType("ByteBufferMemory"),
NodeList.nodeList(new NameExpr("limits")))));

type.addMethod("createTable", Modifier.Keyword.PUBLIC, Modifier.Keyword.STATIC)
.addParameter(parseType("Table"), "table")
.addParameter(parseType("int"), "initValue")
.setType(TableInstance.class)
.createBody()
.addStatement(
new ReturnStmt(
new ObjectCreationExpr(
null,
parseClassOrInterfaceType("TableInstance"),
NodeList.nodeList(
new NameExpr("table"),
new NameExpr("initValue")))));
}

private static void generateWasmModuleHolderInnerClass(
ClassOrInterfaceDeclaration type, String moduleName, String wasmName) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ private CtxBuffer() {}
public static final int TRAP_UNALIGNED_ATOMIC = 12;
public static final int TRAP_INTERRUPTED = 13;

/**
* A host function threw. The throwable itself is held by the runner; this only
* marks the context so compiled code unwinds instead of running on.
*/
public static final int TRAP_HOST_EXCEPTION = 14;

public static final int TABLE_SIZE_OFFSET = 0;
public static final int TABLE_MAX_OFFSET = 4;
public static final int TABLE_ENTRIES_OFFSET = 8;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.Modifier;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.Parameter;
Expand Down Expand Up @@ -103,7 +104,7 @@ public void extendGeneratedSources() throws IOException {
generateLoadNativeCodeMethod(type);
generateNativeProviderMethod(type);
generateBuilderMethod(type, baseName);
generateSafeBuilderMethod(type, baseName);
generateImportFactoryMethods(type);

Files.writeString(sourceFile, cu.toString());
}
Expand Down Expand Up @@ -310,6 +311,88 @@ private static void generateNativeProviderMethod(ClassOrInterfaceDeclaration typ
new NameExpr("NativeMachineFactoryProvider"), "discover")));
}

/**
* Replaces the plain import factories with ones that go through the native
* provider when there is one. Compiled code reaches into a memory or table
* through a raw base address, so an imported one has to come from the same
* backend that will run the module. Falling back leaves the plain types,
* which is what the bytecode path expects.
*
* <p>Generates:
* <code>
* public static Memory createMemory(MemoryLimits limits) {
* var provider = nativeProvider();
* if (provider.isPresent()) {
* return provider.get().createMemory(limits);
* }
* return new ByteBufferMemory(limits);
* }
* </code>
* and the same shape for createTable.
*/
private static void generateImportFactoryMethods(ClassOrInterfaceDeclaration type) {
// addMethod appends, so the base generator's versions have to go first.
type.getMethodsByName("createMemory").forEach(Node::remove);
type.getMethodsByName("createTable").forEach(Node::remove);

var memory =
type.addMethod("createMemory", Modifier.Keyword.PUBLIC, Modifier.Keyword.STATIC)
.addParameter(parseType("MemoryLimits"), "limits")
.setType(parseType("Memory"));
memory.createBody()
.addStatement(providerVar())
.addStatement(
ifProviderPresent(
new MethodCallExpr(
new MethodCallExpr(new NameExpr("provider"), "get"),
"createMemory",
new NodeList<>(new NameExpr("limits")))))
.addStatement(
new ReturnStmt(
new com.github.javaparser.ast.expr.ObjectCreationExpr(
null,
parseClassOrInterfaceType("ByteBufferMemory"),
new NodeList<>(new NameExpr("limits")))));

var table =
type.addMethod("createTable", Modifier.Keyword.PUBLIC, Modifier.Keyword.STATIC)
.addParameter(parseType("Table"), "table")
.addParameter(parseType("int"), "initValue")
.setType(parseType("TableInstance"));
table.createBody()
.addStatement(providerVar())
.addStatement(
ifProviderPresent(
new MethodCallExpr(
new MethodCallExpr(new NameExpr("provider"), "get"),
"createImportTable",
new NodeList<>(
new NameExpr("table"), new NameExpr("initValue")))))
.addStatement(
new ReturnStmt(
new com.github.javaparser.ast.expr.ObjectCreationExpr(
null,
parseClassOrInterfaceType("TableInstance"),
new NodeList<>(
new NameExpr("table"),
new NameExpr("initValue")))));
}

/** {@code var provider = nativeProvider();} */
private static ExpressionStmt providerVar() {
return new ExpressionStmt(
new VariableDeclarationExpr(
new VariableDeclarator(
new VarType(), "provider", new MethodCallExpr("nativeProvider"))));
}

/** {@code if (provider.isPresent()) { return <call>; }} */
private static IfStmt ifProviderPresent(MethodCallExpr call) {
return new IfStmt()
.setCondition(new MethodCallExpr(new NameExpr("provider"), "isPresent"))
.setThenStmt(new BlockStmt(new NodeList<>(new ReturnStmt(call))));
}

private static void generateBuilderMethod(ClassOrInterfaceDeclaration type, String moduleName) {
// Generates:
// <code>
Expand All @@ -326,6 +409,9 @@ private static void generateBuilderMethod(ClassOrInterfaceDeclaration type, Stri
// The native path is selected through nativeProvider() so that callers
// checking it see exactly the decision this method makes. Falling back means
// the build-time compiled bytecode, not the interpreter.
// The base generator already emitted a builder(); addMethod appends rather
// than replaces, so it has to go before this one is added.
type.getMethodsByName("builder").forEach(Node::remove);
var method =
type.addMethod("builder", Modifier.Keyword.PUBLIC, Modifier.Keyword.STATIC)
.setType(parseClassOrInterfaceType("Instance.Builder"));
Expand Down Expand Up @@ -368,23 +454,6 @@ private static void generateBuilderMethod(ClassOrInterfaceDeclaration type, Stri
body.addStatement(new ReturnStmt(compiledBuilder(new NameExpr("module"), moduleName)));
}

private static void generateSafeBuilderMethod(
ClassOrInterfaceDeclaration type, String moduleName) {
// Generates:
// <code>
// public static Instance.Builder safeBuilder() {
// return Instance.builder(load()).withMachineFactory(<moduleName>::create);
// }
// </code>
var method =
type.addMethod("safeBuilder", Modifier.Keyword.PUBLIC, Modifier.Keyword.STATIC)
.setType(parseClassOrInterfaceType("Instance.Builder"));

method.createBody()
.addStatement(
new ReturnStmt(compiledBuilder(new MethodCallExpr("load"), moduleName)));
}

/**
* {@code Instance.builder(<module>).withMachineFactory(<moduleName>::create)}
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,16 +140,14 @@ ValType resolveGlobalType(int globalIdx) {
return module.globalSection().getGlobal(moduleGlobalIdx).valueType();
}

/**
* Widens a value for the argument buffer a host import reads. i32 is
* sign-extended so a host handed -1 sees -1, matching the interpreter, rather
* than 4294967295.
*/
int widenToI64(int valId, ValType type) {
if (type.equals(ValType.I32)) {
return bridge.exports().emitUextendI64(valId);
}
return valId;
}

int narrowFromI64(int valId, ValType type) {
if (type.equals(ValType.I32)) {
return bridge.exports().emitIreduceI32(valId);
return bridge.exports().emitSextendI64(valId);
}
return valId;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,24 @@ static void fillTrapBlock(EmitContext ctx, int trapBlock, int trapCode) {
ctx.emitReturnForFuncType();
}

/**
* A callee that traps records its code and returns like any other call, so the
* caller has to look for it. Without this check the caller runs on to
* completion after the trap: its stores land and its host imports fire.
*/
static void emitTrapCheck(EmitContext ctx) {
var b = ctx.bridge.exports();
int zero = b.emitIconst32(0);
int trapCode = b.emitLoadI32(b.useVar(ctx.ctxPtrVar), zero, CtxBuffer.TRAP_CODE);
int trapped = b.emitIcmp(1, trapCode, b.emitIconst32(0));
int propagateBlock = b.createBlock();
int continueBlock = b.createBlock();
b.emitBrif(trapped, propagateBlock, continueBlock);
b.switchToBlock(propagateBlock);
ctx.emitReturnForFuncType();
b.switchToBlock(continueBlock);
}

// --- Extensions ---

static void emitI32Extend8S(EmitContext ctx) {
Expand Down Expand Up @@ -768,6 +786,7 @@ static void emitCall(EmitContext ctx, AnnotatedInstruction ins) {
}

int rawResult = ctx.bridge.exports().emitCallIndirect(sigRef, funcPtr);
emitTrapCheck(ctx);

if (calleeMultiReturn) {
// Read return values from argsBuffer
Expand Down Expand Up @@ -887,6 +906,7 @@ static void emitCallIndirect(EmitContext ctx, AnnotatedInstruction ins) {
}

int rawResult = b.emitCallIndirect(sigRef, funcPtr);
emitTrapCheck(ctx);

// 9. Handle results
if (calleeMultiReturn) {
Expand Down
3 changes: 1 addition & 2 deletions redline/runner-jffi-tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,7 @@
SpecV1ImportsTest.test118, SpecV1ImportsTest.test119, SpecV1ImportsTest.test120,
SpecV1ImportsTest.test123, SpecV1ImportsTest.test124, SpecV1ImportsTest.test125,
SpecV1ImportsTest.test127, SpecV1ImportsTest.test128, SpecV1ImportsTest.test129,
SpecV1LinkingTest.test129, SpecV1LinkingTest.test130, SpecV1LinkingTest.test131,
SpecV1StartTest.test18,
SpecV1LinkingTest.test130, SpecV1LinkingTest.test131,
SpecV1FuncTest.test85,
SpecV1ThreadsImportsTest.test64, SpecV1ThreadsImportsTest.test65,
SpecV1ThreadsImportsTest.test66,
Expand Down
Loading
Loading