diff --git a/bindgen/it/pom.xml b/bindgen/it/pom.xml new file mode 100644 index 000000000..fbe024187 --- /dev/null +++ b/bindgen/it/pom.xml @@ -0,0 +1,87 @@ + + + 4.0.0 + + + com.dylibso.chicory + bindgen-parent + 999-SNAPSHOT + ../pom.xml + + bindgen-it + jar + Chicory - Bindgen - IT + Integration tests for the Chicory Bindgen + + + + com.dylibso.chicory + bindgen + + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + ${project.basedir} + ${project.version} + + + + + + integration-test + verify + + + + + + org.apache.maven.plugins + maven-invoker-plugin + + ${project.build.directory}/it + true + src/it/settings.xml + verify + true + ${skipTests} + true + invoker.properties + + ${project.version} + ${project.artifactId} + ${project.groupId} + + + + + integration-tests + + install + run + + + + + + + org.codehaus.mojo + templating-maven-plugin + 3.0.0 + + + filtering-java-templates + + filter-sources + + + + + + + diff --git a/bindgen/it/src/it/base/invoker.properties b/bindgen/it/src/it/base/invoker.properties new file mode 100644 index 000000000..84099bc5a --- /dev/null +++ b/bindgen/it/src/it/base/invoker.properties @@ -0,0 +1 @@ +invoker.goals=test diff --git a/bindgen/it/src/it/base/pom.xml b/bindgen/it/src/it/base/pom.xml new file mode 100644 index 000000000..a0b294234 --- /dev/null +++ b/bindgen/it/src/it/base/pom.xml @@ -0,0 +1,70 @@ + + + + 4.0.0 + com.dylibso.chicory + + base-chicory-it + 0.0-SNAPSHOT + jar + + + @maven.compiler.release@ + + + + + com.dylibso.chicory + runtime + @project.version@ + + + com.dylibso.chicory + wasm-corpus + @project.version@ + + + + org.junit.jupiter + junit-jupiter-api + @junit.version@ + test + + + org.junit.jupiter + junit-jupiter-engine + @junit.version@ + test + + + + + + + com.dylibso.chicory + bindgen + @project.version@ + + + + generate + + + @project.basedir@/../../wasm-corpus/src/main/resources/compiled/extism-runtime.wasm + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + @maven-compiler-plugin.version@ + + ${maven.compiler.release} + + + + + + diff --git a/bindgen/it/src/it/base/src/test/java/BasicTest.java b/bindgen/it/src/it/base/src/test/java/BasicTest.java new file mode 100644 index 000000000..8fe868a52 --- /dev/null +++ b/bindgen/it/src/it/base/src/test/java/BasicTest.java @@ -0,0 +1,40 @@ +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.dylibso.chicory.gen.Module; +import com.dylibso.chicory.runtime.Instance; +import com.dylibso.chicory.wasm.Parser; +import org.junit.jupiter.api.Test; + +class BasicTest { + + class TestModule extends Module { + private final Instance instance; + + public TestModule() { + instance = + Instance.builder( + Parser.parse( + BasicTest.class.getResourceAsStream( + "/compiled/extism-runtime.wasm"))) + .build(); + } + + @Override + public Instance instance() { + return this.instance; + } + } + + @Test + public void basicModule() { + // Arrange + var extismModule = new TestModule(); + + // Act + var ptr = extismModule.alloc(1); + + // Assert + assertTrue(ptr > 0); + extismModule.free(ptr); + } +} diff --git a/bindgen/it/src/it/multi-returns/invoker.properties b/bindgen/it/src/it/multi-returns/invoker.properties new file mode 100644 index 000000000..84099bc5a --- /dev/null +++ b/bindgen/it/src/it/multi-returns/invoker.properties @@ -0,0 +1 @@ +invoker.goals=test diff --git a/bindgen/it/src/it/multi-returns/pom.xml b/bindgen/it/src/it/multi-returns/pom.xml new file mode 100644 index 000000000..76dd822a9 --- /dev/null +++ b/bindgen/it/src/it/multi-returns/pom.xml @@ -0,0 +1,70 @@ + + + + 4.0.0 + com.dylibso.chicory + + multi-returns-chicory-it + 0.0-SNAPSHOT + jar + + + @maven.compiler.release@ + + + + + com.dylibso.chicory + runtime + @project.version@ + + + com.dylibso.chicory + wasm-corpus + @project.version@ + + + + org.junit.jupiter + junit-jupiter-api + @junit.version@ + test + + + org.junit.jupiter + junit-jupiter-engine + @junit.version@ + test + + + + + + + com.dylibso.chicory + bindgen + @project.version@ + + + + generate + + + @project.basedir@/../../wasm-corpus/src/main/resources/compiled/multi-returns.wat.wasm + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + @maven-compiler-plugin.version@ + + ${maven.compiler.release} + + + + + + diff --git a/bindgen/it/src/it/multi-returns/src/test/java/MultiReturnsTest.java b/bindgen/it/src/it/multi-returns/src/test/java/MultiReturnsTest.java new file mode 100644 index 000000000..846f09531 --- /dev/null +++ b/bindgen/it/src/it/multi-returns/src/test/java/MultiReturnsTest.java @@ -0,0 +1,41 @@ +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.dylibso.chicory.gen.Module; +import com.dylibso.chicory.runtime.Instance; +import com.dylibso.chicory.wasm.Parser; +import org.junit.jupiter.api.Test; + +class MultiReturnsTest { + + class TestModule extends Module { + private final Instance instance; + + public TestModule() { + instance = + Instance.builder( + Parser.parse( + MultiReturnsTest.class.getResourceAsStream( + "/compiled/multi-returns.wat.wasm"))) + .build(); + } + + @Override + public Instance instance() { + return this.instance; + } + } + + @Test + public void multiReturnsModule() { + // Arrange + var multiReturnsModule = new TestModule(); + + // Act + var result = multiReturnsModule.example(1L); + + // Assert + assertEquals(2, result.length); + assertEquals(1L, result[0].asLong()); + assertEquals(1L, result[1].asLong()); + } +} diff --git a/bindgen/it/src/it/opa/invoker.properties b/bindgen/it/src/it/opa/invoker.properties new file mode 100644 index 000000000..84099bc5a --- /dev/null +++ b/bindgen/it/src/it/opa/invoker.properties @@ -0,0 +1 @@ +invoker.goals=test diff --git a/bindgen/it/src/it/opa/pom.xml b/bindgen/it/src/it/opa/pom.xml new file mode 100644 index 000000000..edd70537c --- /dev/null +++ b/bindgen/it/src/it/opa/pom.xml @@ -0,0 +1,77 @@ + + + + 4.0.0 + com.dylibso.chicory + + opa-chicory-it + 0.0-SNAPSHOT + jar + + + @maven.compiler.release@ + + + + + com.dylibso.chicory + runtime + @project.version@ + + + com.dylibso.chicory + wasm-corpus + @project.version@ + + + com.dylibso.chicory + function-processor + @project.version@ + provided + + + + org.junit.jupiter + junit-jupiter-api + @junit.version@ + test + + + org.junit.jupiter + junit-jupiter-engine + @junit.version@ + test + + + + + + + com.dylibso.chicory + bindgen + @project.version@ + + + + generate + + + OpaModule + ${project.basedir}/src/test/resources/policy.wasm + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + @maven-compiler-plugin.version@ + + ${maven.compiler.release} + + + + + + diff --git a/bindgen/it/src/it/opa/src/test/java/OpaTest.java b/bindgen/it/src/it/opa/src/test/java/OpaTest.java new file mode 100644 index 000000000..4275ab771 --- /dev/null +++ b/bindgen/it/src/it/opa/src/test/java/OpaTest.java @@ -0,0 +1,39 @@ +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class OpaTest { + + @Test + public void opaModule() { + // Arrange + var opa = new OpaTestModule(); + + // Act + var ctxAddr = opa.opaEvalCtxNew(); + var input = "{\"user\": \"alice\"}"; + var inputStrAddr = opa.opaMalloc(input.length()); + opa.instance().memory().writeCString(inputStrAddr, input); + var inputAddr = opa.opaJsonParse(inputStrAddr, input.length()); + opa.opaFree(inputStrAddr); + opa.opaEvalCtxSetInput(ctxAddr, inputAddr); + + var data = "{ \"role\" : { \"alice\" : \"admin\", \"bob\" : \"user\" } }"; + var dataStrAddr = opa.opaMalloc(data.length()); + opa.instance().memory().writeCString(dataStrAddr, data); + var dataAddr = opa.opaJsonParse(dataStrAddr, data.length()); + opa.opaFree(dataStrAddr); + opa.opaEvalCtxSetData(ctxAddr, dataAddr); + + var evalResult = opa.eval(ctxAddr); + + int resultAddr = opa.opaEvalCtxGetResult(ctxAddr); + int resultStrAddr = opa.opaJsonDump(resultAddr); + var resultStr = opa.instance().memory().readCString(resultStrAddr); + opa.opaFree(resultStrAddr); + + // Assert + assertEquals(0, evalResult); + assertEquals("[{\"result\":true}]", resultStr); + } +} diff --git a/bindgen/it/src/it/opa/src/test/java/OpaTestModule.java b/bindgen/it/src/it/opa/src/test/java/OpaTestModule.java new file mode 100644 index 000000000..c2b18238f --- /dev/null +++ b/bindgen/it/src/it/opa/src/test/java/OpaTestModule.java @@ -0,0 +1,77 @@ +import com.dylibso.chicory.function.annotations.HostModule; +import com.dylibso.chicory.function.annotations.WasmExport; +import com.dylibso.chicory.gen.OpaModule; +import com.dylibso.chicory.runtime.HostImports; +import com.dylibso.chicory.runtime.Instance; +import com.dylibso.chicory.runtime.Memory; +import com.dylibso.chicory.wasm.Parser; +import com.dylibso.chicory.wasm.types.MemoryLimits; +import java.util.Arrays; +import java.util.List; + +@HostModule("env") +class OpaTestModule extends OpaModule { + private final Memory memory; + private final Instance instance; + + public OpaTestModule() { + this.memory = new Memory(new MemoryLimits(10)); + this.instance = + Instance.builder(Parser.parse(OpaTest.class.getResourceAsStream("/policy.wasm"))) + .withHostImports( + HostImports.builder() + .withFunctions( + Arrays.asList( + OpaTestModule_ModuleFactory.toHostFunctions( + this))) + .withMemories(List.of(toHostMemory())) + .build()) + .build(); + } + + @Override + public Memory memory() { + return this.memory; + } + + @Override + public Instance instance() { + return this.instance; + } + + @WasmExport + @Override + public int opaBuiltin0(int arg0, int arg1) { + throw new RuntimeException("opa_builtin0 - not implemented"); + } + + @WasmExport + @Override + public int opaBuiltin1(int arg0, int arg1, int arg2) { + throw new RuntimeException("opa_builtin1 - not implemented"); + } + + @WasmExport + @Override + public int opaBuiltin2(int arg0, int arg1, int arg2, int arg3) { + throw new RuntimeException("opa_builtin2 - not implemented"); + } + + @WasmExport + @Override + public int opaBuiltin3(int arg0, int arg1, int arg2, int arg3, int arg4) { + throw new RuntimeException("opa_builtin3 - not implemented"); + } + + @WasmExport + @Override + public int opaBuiltin4(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) { + throw new RuntimeException("opa_builtin4 - not implemented"); + } + + @WasmExport + @Override + public void opaAbort(int arg0) { + System.exit(arg0); + } +} diff --git a/bindgen/it/src/it/opa/src/test/resources/policy.wasm b/bindgen/it/src/it/opa/src/test/resources/policy.wasm new file mode 100644 index 000000000..efe2c4f4c Binary files /dev/null and b/bindgen/it/src/it/opa/src/test/resources/policy.wasm differ diff --git a/bindgen/it/src/it/settings.xml b/bindgen/it/src/it/settings.xml new file mode 100644 index 000000000..2d90068bb --- /dev/null +++ b/bindgen/it/src/it/settings.xml @@ -0,0 +1,35 @@ + + + + + it-repo + + true + + + + local.central + @localRepositoryUrl@ + + true + + + true + + + + + + local.central + @localRepositoryUrl@ + + true + + + true + + + + + + diff --git a/bindgen/it/src/it/with-imports/invoker.properties b/bindgen/it/src/it/with-imports/invoker.properties new file mode 100644 index 000000000..84099bc5a --- /dev/null +++ b/bindgen/it/src/it/with-imports/invoker.properties @@ -0,0 +1 @@ +invoker.goals=test diff --git a/bindgen/it/src/it/with-imports/pom.xml b/bindgen/it/src/it/with-imports/pom.xml new file mode 100644 index 000000000..385a2e805 --- /dev/null +++ b/bindgen/it/src/it/with-imports/pom.xml @@ -0,0 +1,76 @@ + + + + 4.0.0 + com.dylibso.chicory + + with-imports-chicory-it + 0.0-SNAPSHOT + jar + + + @maven.compiler.release@ + + + + + com.dylibso.chicory + runtime + @project.version@ + + + com.dylibso.chicory + wasm-corpus + @project.version@ + + + com.dylibso.chicory + function-processor + @project.version@ + provided + + + + org.junit.jupiter + junit-jupiter-api + @junit.version@ + test + + + org.junit.jupiter + junit-jupiter-engine + @junit.version@ + test + + + + + + + com.dylibso.chicory + bindgen + @project.version@ + + + + generate + + + @project.basedir@/../../wasm-corpus/src/main/resources/compiled/host-function.wat.wasm + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + @maven-compiler-plugin.version@ + + ${maven.compiler.release} + + + + + + diff --git a/bindgen/it/src/it/with-imports/src/test/java/chicory/test/WithImportsTest.java b/bindgen/it/src/it/with-imports/src/test/java/chicory/test/WithImportsTest.java new file mode 100644 index 000000000..81aed653f --- /dev/null +++ b/bindgen/it/src/it/with-imports/src/test/java/chicory/test/WithImportsTest.java @@ -0,0 +1,60 @@ +package chicory.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.dylibso.chicory.function.annotations.HostModule; +import com.dylibso.chicory.function.annotations.WasmExport; +import com.dylibso.chicory.gen.Module; +import com.dylibso.chicory.runtime.HostImports; +import com.dylibso.chicory.runtime.Instance; +import com.dylibso.chicory.wasm.Parser; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.Test; + +class WithImportsTest { + public final AtomicInteger count = new AtomicInteger(); + + @HostModule("console") + class TestModule extends Module { + private final Instance instance; + private static final String EXPECTED = "Hello, World!"; + + public TestModule() { + instance = + Instance.builder( + Parser.parse( + WithImportsTest.class.getResourceAsStream( + "/compiled/host-function.wat.wasm"))) + .withHostImports( + new HostImports(TestModule_ModuleFactory.toHostFunctions(this))) + .build(); + } + + @Override + public Instance instance() { + return this.instance; + } + + @WasmExport + @Override + public void log(int len, int offset) { + var message = instance.memory().readString(offset, len); + + if (EXPECTED.equals(message)) { + count.incrementAndGet(); + } + } + } + + @Test + public void withImportsModule() { + // Arrange + var withImportsModule = new TestModule(); + + // Act + withImportsModule.logIt(); + + // Assert + assertEquals(10, count.get()); + } +} diff --git a/bindgen/plugin/pom.xml b/bindgen/plugin/pom.xml new file mode 100644 index 000000000..15082cf6c --- /dev/null +++ b/bindgen/plugin/pom.xml @@ -0,0 +1,63 @@ + + + 4.0.0 + + + com.dylibso.chicory + bindgen-parent + 999-SNAPSHOT + ../pom.xml + + bindgen + maven-plugin + Chicory - Bindgen + A Maven Plugin to generate bindings for WebAssembly modules + + + + com.dylibso.chicory + wasm + + + com.github.javaparser + javaparser-core + ${javaparser.version} + + + org.apache.maven + maven-core + ${maven-plugin-api.version} + provided + + + org.apache.maven + maven-plugin-api + ${maven-plugin-api.version} + provided + + + org.apache.maven.plugin-tools + maven-plugin-annotations + ${maven-plugin-annotations.version} + provided + + + + + + + org.apache.maven.plugins + maven-plugin-plugin + + prefix + + + + default-descriptor + process-classes + + + + + + diff --git a/bindgen/plugin/src/main/java/com/dylibso/chicory/BindGenMojo.java b/bindgen/plugin/src/main/java/com/dylibso/chicory/BindGenMojo.java new file mode 100644 index 000000000..0b7c26e5c --- /dev/null +++ b/bindgen/plugin/src/main/java/com/dylibso/chicory/BindGenMojo.java @@ -0,0 +1,380 @@ +package com.dylibso.chicory; + +import static com.github.javaparser.StaticJavaParser.parseType; +import static com.github.javaparser.utils.StringEscapeUtils.escapeJava; +import static org.apache.maven.plugins.annotations.LifecyclePhase.GENERATE_SOURCES; + +import com.dylibso.chicory.wasm.Parser; +import com.dylibso.chicory.wasm.types.ExternalType; +import com.dylibso.chicory.wasm.types.FunctionImport; +import com.dylibso.chicory.wasm.types.ValueType; +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.Modifier; +import com.github.javaparser.ast.NodeList; +import com.github.javaparser.ast.expr.ArrayAccessExpr; +import com.github.javaparser.ast.expr.ArrayCreationExpr; +import com.github.javaparser.ast.expr.ArrayInitializerExpr; +import com.github.javaparser.ast.expr.Expression; +import com.github.javaparser.ast.expr.IntegerLiteralExpr; +import com.github.javaparser.ast.expr.MethodCallExpr; +import com.github.javaparser.ast.expr.NameExpr; +import com.github.javaparser.ast.expr.ObjectCreationExpr; +import com.github.javaparser.ast.expr.SimpleName; +import com.github.javaparser.ast.expr.StringLiteralExpr; +import com.github.javaparser.ast.stmt.BlockStmt; +import com.github.javaparser.ast.stmt.ReturnStmt; +import com.github.javaparser.utils.SourceRoot; +import java.io.File; +import java.nio.file.Path; +import java.util.List; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.logging.Log; +import org.apache.maven.plugin.logging.SystemStreamLog; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; + +/** + * This plugin will generate bindings for Wasm modules + */ +@Mojo(name = "generate", defaultPhase = GENERATE_SOURCES, threadSafe = true) +public class BindGenMojo extends AbstractMojo { + + private final Log log = new SystemStreamLog(); + + /** + * Package name + */ + @Parameter(required = true, defaultValue = "com.dylibso.chicory.gen") + private String packageName; + + /** + * Name of the bindings + */ + @Parameter(required = true, defaultValue = "Module") + private String name; + + /** + * Ignore modules + */ + @Parameter(required = true, defaultValue = "[]]") + private List ignoredModules; + + // TODO: check multiple executions on multiple files + /** + * Location of the source wasm file + */ + @Parameter(required = true) + private File source; + + /** + * Location for the binding generated sources. + */ + @Parameter( + required = true, + defaultValue = "${project.build.directory}/generated-sources/chciory-bindgen") + private File sourceDestinationFolder; + + /** + * The current Maven project. + */ + @Parameter(property = "project", required = true, readonly = true) + private MavenProject project; + + private static String camelCase(String s) { + var sb = new StringBuilder(); + boolean toUpper = false; + for (int i = 0; i < s.length(); i++) { + var c = s.charAt(i); + if (c == '_') { + toUpper = true; + } else if (toUpper) { + sb.append(Character.toUpperCase(c)); + toUpper = false; + } else { + sb.append(c); + } + } + return sb.toString(); + } + + private boolean isSupported(ValueType vt) { + switch (vt) { + case I32: + case I64: + case F32: + case F64: + return true; + default: + return false; + } + } + + private String valueTypeToJava(ValueType vt) { + switch (vt) { + case I32: + return "int"; + case I64: + return "long"; + case F32: + return "float"; + case F64: + return "double"; + default: + throw new IllegalArgumentException("type not supported " + vt); + } + } + + private String valueTypeToConverter(ValueType vt) { + switch (vt) { + case I32: + return "asInt"; + case I64: + return "asLong"; + case F32: + return "asFloat"; + case F64: + return "asDouble"; + default: + throw new IllegalArgumentException("type not supported " + vt); + } + } + + private String javaToValueTypeToConverter(ValueType vt) { + switch (vt) { + case I32: + return "i32"; + case I64: + return "i64"; + case F32: + return "f32"; + case F64: + return "f64"; + default: + throw new IllegalArgumentException("type not supported " + vt); + } + } + + @SuppressWarnings("StringSplitter") + private Path qualifiedDestinationFolder() { + var res = sourceDestinationFolder.toPath(); + for (var p : packageName.split("\\.")) { + res = res.resolve(p); + } + return res; + } + + @Override + @SuppressWarnings("deprecation") + public void execute() throws MojoExecutionException { + // Create destination folders + if (!sourceDestinationFolder.mkdirs()) { + log.warn("Failed to create folder: " + sourceDestinationFolder); + } + + // load the user wasm module + var module = Parser.parse(source); + + // generate the bindings + final SourceRoot dest = new SourceRoot(sourceDestinationFolder.toPath()); + + var cu = new CompilationUnit(packageName); + cu.setStorage(qualifiedDestinationFolder().resolve(name + ".java")); + + cu.addImport("java.util.List"); + cu.addImport("com.dylibso.chicory.wasm.types.Value"); + cu.addImport("com.dylibso.chicory.wasm.types.ValueType"); + cu.addImport("com.dylibso.chicory.runtime.HostFunction"); + cu.addImport("com.dylibso.chicory.runtime.HostMemory"); + cu.addImport("com.dylibso.chicory.runtime.Instance"); + cu.addImport("com.dylibso.chicory.runtime.Memory"); + + var clazz = cu.addClass(name, Modifier.Keyword.ABSTRACT, Modifier.Keyword.PUBLIC); + var instanceMethod = + clazz.addMethod("instance", Modifier.Keyword.PUBLIC, Modifier.Keyword.ABSTRACT); + instanceMethod.setType("Instance").removeBody(); + var instanceCall = new MethodCallExpr("instance"); + + int importedFunctionCount = 0; + boolean importedMemory = false; + + for (int importIdx = 0; importIdx < module.importSection().importCount(); importIdx++) { + var imprt = module.importSection().getImport(importIdx); + + if (imprt.importType() == ExternalType.MEMORY) { + var method = + clazz.addMethod( + camelCase(imprt.name()), + Modifier.Keyword.PUBLIC, + Modifier.Keyword.ABSTRACT); + method.setType("Memory").removeBody(); + var toHostMemsMethod = clazz.addMethod("toHostMemory", Modifier.Keyword.PUBLIC); + toHostMemsMethod.setType("HostMemory"); + var toHostMemsBody = toHostMemsMethod.createBody(); + var memMapping = + new ObjectCreationExpr() + .setType("HostMemory") + .addArgument(new StringLiteralExpr(imprt.moduleName())) + .addArgument(new StringLiteralExpr(imprt.name())) + .addArgument(new MethodCallExpr("memory")); + toHostMemsBody.addStatement(new ReturnStmt(memMapping)); + importedMemory = true; + } else if (imprt.importType() == ExternalType.FUNCTION) { + importedFunctionCount++; + var importName = camelCase(imprt.name()); + var method = + clazz.addMethod( + importName, Modifier.Keyword.PUBLIC, Modifier.Keyword.ABSTRACT); + + if (ignoredModules.contains(imprt.moduleName())) { + log.warn("Skipping generation for imported module " + imprt.moduleName()); + continue; + } + var importType = module.typeSection().getType(((FunctionImport) imprt).typeIndex()); + var functionInvoc = new MethodCallExpr(importName); + var handleBody = new BlockStmt(); + + for (int paramIdx = 0; paramIdx < importType.params().size(); paramIdx++) { + var argName = "arg" + paramIdx; + functionInvoc.addArgument( + new MethodCallExpr( + new ArrayAccessExpr( + new NameExpr("args"), new IntegerLiteralExpr(paramIdx)), + new SimpleName( + valueTypeToConverter( + importType.params().get(paramIdx))))); + method.addParameter( + valueTypeToJava(importType.params().get(paramIdx)), argName); + } + method.removeBody(); + + if (importType.returns().size() == 0) { + method.setType("void"); + handleBody.addStatement(functionInvoc); + handleBody.addStatement( + new ReturnStmt(new ArrayCreationExpr(parseType("Value")))); + } else if (importType.returns().size() == 1 + && isSupported(importType.returns().get(0))) { + method.setType(valueTypeToJava(importType.returns().get(0))); + + var arrayReturn = new ArrayCreationExpr(parseType("Value")); + var arrayReturnInit = new ArrayInitializerExpr(); + arrayReturnInit.setValues( + NodeList.nodeList( + new MethodCallExpr( + new NameExpr("Value"), + new SimpleName( + javaToValueTypeToConverter( + importType.returns().get(0))), + NodeList.nodeList(functionInvoc)))); + + arrayReturn.setInitializer(arrayReturnInit); + + handleBody.addStatement(new ReturnStmt(arrayReturn)); + } else { + method.setType("Value[]"); + handleBody.addStatement(new ReturnStmt(functionInvoc)); + } + } else { + // TODO: explicitly export also the other + log.warn( + "[chicory-bindgen] no generated code for import type: " + + imprt.moduleName() + + "." + + imprt.name() + + " of type " + + imprt.importType()); + } + } + + // now the exports + for (int exportIdx = 0; exportIdx < module.exportSection().exportCount(); exportIdx++) { + var export = module.exportSection().getExport(exportIdx); + + if (export.exportType() == ExternalType.MEMORY && !importedMemory) { + var method = clazz.addMethod(camelCase(export.name()), Modifier.Keyword.PUBLIC); + method.setType("Memory"); + var body = method.createBody(); + body.addStatement( + new ReturnStmt(new MethodCallExpr(instanceCall, new SimpleName("memory")))); + } else if (export.exportType() == ExternalType.FUNCTION) { + var method = clazz.addMethod(camelCase(export.name()), Modifier.Keyword.PUBLIC); + + var funcTypeIdx = + module.functionSection() + .getFunctionType(export.index() - importedFunctionCount); + var exportType = module.typeSection().getType(funcTypeIdx); + + var returnType = "Value[]"; + var hasReturns = false; + var hasJavaReturn = false; + String javaReturnConverter = null; + var returns = exportType.returns(); + switch (returns.size()) { + case 0: + returnType = "void"; + hasReturns = false; + break; + case 1: + if (isSupported(returns.get(0))) { + returnType = valueTypeToJava(returns.get(0)); + hasJavaReturn = true; + javaReturnConverter = valueTypeToConverter(returns.get(0)); + } + // fallthrough + default: + hasReturns = true; + break; + } + method.setType(returnType); + + var params = exportType.params(); + Expression[] args = new Expression[params.size()]; + for (int paramIdx = 0; paramIdx < params.size(); paramIdx++) { + var argName = "arg" + paramIdx; + method.addParameter(valueTypeToJava(params.get(paramIdx)), argName); + args[paramIdx] = + new MethodCallExpr( + new NameExpr("Value"), + new SimpleName( + javaToValueTypeToConverter(params.get(paramIdx))), + NodeList.nodeList(new NameExpr(argName))); + } + + var methodBody = method.createBody(); + Expression methodCall = + new MethodCallExpr( + new MethodCallExpr( + instanceCall, + new SimpleName("export"), + NodeList.nodeList( + new StringLiteralExpr(escapeJava(export.name())))), + new SimpleName("apply"), + NodeList.nodeList(args)); + if (!hasReturns) { + methodBody.addStatement(methodCall); + } else { + if (hasJavaReturn) { + methodCall = + new MethodCallExpr( + new ArrayAccessExpr(methodCall, new IntegerLiteralExpr()), + new SimpleName(javaReturnConverter)); + } + methodBody.addStatement(new ReturnStmt(methodCall)); + } + } else { + // TODO: explicitly export also the other types + log.warn( + "[chicory-bindgen] no generated code for export type: " + + export.exportType()); + } + } + + dest.add(cu); + dest.saveAll(); + + // Add the generated tests to the source root + project.addTestCompileSourceRoot(sourceDestinationFolder.getAbsolutePath()); + } +} diff --git a/bindgen/pom.xml b/bindgen/pom.xml new file mode 100644 index 000000000..f7110e972 --- /dev/null +++ b/bindgen/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + + com.dylibso.chicory + chicory + 999-SNAPSHOT + ../pom.xml + + bindgen-parent + pom + Chicory - Bindgen - Parent + Parent module for the Bindgen + + + it + plugin + + + diff --git a/pom.xml b/pom.xml index 621be6d83..45a32f76d 100644 --- a/pom.xml +++ b/pom.xml @@ -36,6 +36,7 @@ aot aot-tests + bindgen bom cli function-annotations @@ -138,6 +139,11 @@ aot ${project.version} + + com.dylibso.chicory + bindgen + ${project.version} + com.dylibso.chicory cli @@ -407,6 +413,8 @@ **/src/main/java/**/*.java **/src/test/java/**/*.java + **/src/it/**/src/main/java/**/*.java + **/src/it/**/src/test/java/**/*.java 1.18.1 @@ -566,6 +574,7 @@ ${maven.dependency.failOnWarning} true + com.dylibso.chicory:bindgen com.dylibso.chicory:wasm-corpus org.junit.jupiter:junit-jupiter-engine diff --git a/wasm-corpus/src/main/resources/compiled/multi-returns.wat.wasm b/wasm-corpus/src/main/resources/compiled/multi-returns.wat.wasm new file mode 100644 index 000000000..48141a965 Binary files /dev/null and b/wasm-corpus/src/main/resources/compiled/multi-returns.wat.wasm differ diff --git a/wasm-corpus/src/main/resources/wat/multi-returns.wat b/wasm-corpus/src/main/resources/wat/multi-returns.wat new file mode 100644 index 000000000..4630f1517 --- /dev/null +++ b/wasm-corpus/src/main/resources/wat/multi-returns.wat @@ -0,0 +1,6 @@ +(module + (type (;0;) (func (param i64) (result i64 i64))) + (func (;0;) (type 0) (param i64) (result i64 i64) + local.get 0 + local.get 0) + (export "example" (func 0)))