From df87e0d280117afef741a40e2f43823deebcdab8 Mon Sep 17 00:00:00 2001 From: Andrea Peruffo Date: Fri, 30 Aug 2024 18:40:09 +0100 Subject: [PATCH] Introduce experimental bindgen for Wasm Modules --- bindgen/it/pom.xml | 87 ++++ bindgen/it/src/it/base/invoker.properties | 1 + bindgen/it/src/it/base/pom.xml | 70 ++++ .../src/it/base/src/test/java/BasicTest.java | 40 ++ .../src/it/multi-returns/invoker.properties | 1 + bindgen/it/src/it/multi-returns/pom.xml | 70 ++++ .../src/test/java/MultiReturnsTest.java | 41 ++ bindgen/it/src/it/opa/invoker.properties | 1 + bindgen/it/src/it/opa/pom.xml | 77 ++++ .../it/src/it/opa/src/test/java/OpaTest.java | 39 ++ .../it/opa/src/test/java/OpaTestModule.java | 77 ++++ .../src/it/opa/src/test/resources/policy.wasm | Bin 0 -> 139054 bytes bindgen/it/src/it/settings.xml | 35 ++ .../it/src/it/with-imports/invoker.properties | 1 + bindgen/it/src/it/with-imports/pom.xml | 76 ++++ .../java/chicory/test/WithImportsTest.java | 60 +++ bindgen/plugin/pom.xml | 63 +++ .../java/com/dylibso/chicory/BindGenMojo.java | 380 ++++++++++++++++++ bindgen/pom.xml | 21 + pom.xml | 9 + .../resources/compiled/multi-returns.wat.wasm | Bin 0 -> 44 bytes .../src/main/resources/wat/multi-returns.wat | 6 + 22 files changed, 1155 insertions(+) create mode 100644 bindgen/it/pom.xml create mode 100644 bindgen/it/src/it/base/invoker.properties create mode 100644 bindgen/it/src/it/base/pom.xml create mode 100644 bindgen/it/src/it/base/src/test/java/BasicTest.java create mode 100644 bindgen/it/src/it/multi-returns/invoker.properties create mode 100644 bindgen/it/src/it/multi-returns/pom.xml create mode 100644 bindgen/it/src/it/multi-returns/src/test/java/MultiReturnsTest.java create mode 100644 bindgen/it/src/it/opa/invoker.properties create mode 100644 bindgen/it/src/it/opa/pom.xml create mode 100644 bindgen/it/src/it/opa/src/test/java/OpaTest.java create mode 100644 bindgen/it/src/it/opa/src/test/java/OpaTestModule.java create mode 100644 bindgen/it/src/it/opa/src/test/resources/policy.wasm create mode 100644 bindgen/it/src/it/settings.xml create mode 100644 bindgen/it/src/it/with-imports/invoker.properties create mode 100644 bindgen/it/src/it/with-imports/pom.xml create mode 100644 bindgen/it/src/it/with-imports/src/test/java/chicory/test/WithImportsTest.java create mode 100644 bindgen/plugin/pom.xml create mode 100644 bindgen/plugin/src/main/java/com/dylibso/chicory/BindGenMojo.java create mode 100644 bindgen/pom.xml create mode 100644 wasm-corpus/src/main/resources/compiled/multi-returns.wat.wasm create mode 100644 wasm-corpus/src/main/resources/wat/multi-returns.wat 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 0000000000000000000000000000000000000000..efe2c4f4cc30f7d5adce19f1826861ecfbd9234d GIT binary patch literal 139054 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 KdxW)U=zjspsh^7g literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..48141a96558246a3775b48b31ff9979682c4dd53 GIT binary patch literal 44 xcmZQbEY4+QU|?WmXG~zMW2&oTW@2Pu=VoM2tw_u*$Vp{j;NoCpV^9D>ZUCXZ2AKc= literal 0 HcmV?d00001 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)))