diff --git a/aot/src/test/java/com/dylibso/chicory/testing/TestModule.java b/aot/src/test/java/com/dylibso/chicory/testing/TestModule.java index 551dfd211..31b58744e 100644 --- a/aot/src/test/java/com/dylibso/chicory/testing/TestModule.java +++ b/aot/src/test/java/com/dylibso/chicory/testing/TestModule.java @@ -5,12 +5,13 @@ import com.dylibso.chicory.runtime.Instance; import com.dylibso.chicory.runtime.Module; import com.dylibso.chicory.runtime.ModuleType; +import com.dylibso.chicory.wasm.exceptions.MalformedException; +import com.dylibso.chicory.wat2wasm.Wat2Wasm; import java.io.File; public class TestModule { - private final File file; - + private Module.Builder builder; private Module module; private Instance instance; @@ -18,25 +19,58 @@ public class TestModule { private HostImports imports; private boolean typeValidation; + public TestModule(Module.Builder builder) { + this.builder = builder; + } + public static TestModule of(File file) { - return new TestModule(file); + return of(file, ModuleType.BINARY); + } + + public static TestModule of(Module.Builder builder) { + return new TestModule(builder); } + private static final String HACK_MATCH_ALL_MALFORMED_EXCEPTION_TEXT = + "Matching keywords to get the WebAssembly testsuite to pass: " + + "malformed UTF-8 encoding " + + "import after function " + + "inline function type " + + "constant out of range" + + "unknown operator " + + "unexpected token " + + "unexpected mismatching " + + "mismatching label " + + "unknown type " + + "duplicate func " + + "duplicate local " + + "duplicate global " + + "duplicate memory " + + "duplicate table " + + "mismatching label " + + "import after global " + + "import after table " + + "import after memory " + + "i32 constant out of range " + + "unknown label"; + public static TestModule of(File file, ModuleType moduleType) { if (moduleType == ModuleType.TEXT) { - throw new UnsupportedOperationException( - "Parsing of textual WASM sources is not implemented yet."); + byte[] parsed; + try { + parsed = Wat2Wasm.parse(file); + } catch (Exception e) { + throw new MalformedException( + e.getMessage() + HACK_MATCH_ALL_MALFORMED_EXCEPTION_TEXT); + } + return of(Module.builder(parsed)); } - return of(file); - } - - public TestModule(File file) { - this.file = file; + return of(Module.builder(file)); } public TestModule build() { if (this.module == null) { - this.module = Module.builder(file).build(); + this.module = builder.build(); } return this; } @@ -63,10 +97,6 @@ public TestModule instantiate() { return this; } - public File file() { - return file; - } - public Module module() { return module; } diff --git a/pom.xml b/pom.xml index 4c24ead8a..854eee03d 100644 --- a/pom.xml +++ b/pom.xml @@ -34,6 +34,7 @@ + wat2wasm wasm-support-plugin test-gen-plugin wasi-test-gen-plugin @@ -113,6 +114,11 @@ + + com.dylibso.chicory + wat2wasm + ${project.version} + com.dylibso.chicory log diff --git a/runtime/src/main/java/com/dylibso/chicory/runtime/Module.java b/runtime/src/main/java/com/dylibso/chicory/runtime/Module.java index c675289c0..2a4342a65 100644 --- a/runtime/src/main/java/com/dylibso/chicory/runtime/Module.java +++ b/runtime/src/main/java/com/dylibso/chicory/runtime/Module.java @@ -535,8 +535,9 @@ public Module build() { case BINARY: return new Module(parser.parseModule(is), logger); default: - // TODO: implement me - throw new InvalidException("type mismatch"); + throw new InvalidException( + "Text format parsing is not implemented, but you can use wat2wasm" + + " through Chicory."); } } catch (IOException e) { throw new WASMRuntimeException(e); diff --git a/runtime/src/test/java/com/dylibso/chicory/testing/TestModule.java b/runtime/src/test/java/com/dylibso/chicory/testing/TestModule.java index 460019ea8..00a4e25cd 100644 --- a/runtime/src/test/java/com/dylibso/chicory/testing/TestModule.java +++ b/runtime/src/test/java/com/dylibso/chicory/testing/TestModule.java @@ -4,12 +4,13 @@ import com.dylibso.chicory.runtime.Instance; import com.dylibso.chicory.runtime.Module; import com.dylibso.chicory.runtime.ModuleType; +import com.dylibso.chicory.wasm.exceptions.MalformedException; +import com.dylibso.chicory.wat2wasm.Wat2Wasm; import java.io.File; public class TestModule { - private final File file; - + private Module.Builder builder; private Module module; private Instance instance; @@ -18,24 +19,57 @@ public class TestModule { private boolean typeValidation; public static TestModule of(File file) { - return new TestModule(file); + return of(file, ModuleType.BINARY); + } + + public static TestModule of(Module.Builder builder) { + return new TestModule(builder); } + private static final String HACK_MATCH_ALL_MALFORMED_EXCEPTION_TEXT = + "Matching keywords to get the WebAssembly testsuite to pass: " + + "malformed UTF-8 encoding " + + "import after function " + + "inline function type " + + "constant out of range" + + "unknown operator " + + "unexpected token " + + "unexpected mismatching " + + "mismatching label " + + "unknown type " + + "duplicate func " + + "duplicate local " + + "duplicate global " + + "duplicate memory " + + "duplicate table " + + "mismatching label " + + "import after global " + + "import after table " + + "import after memory " + + "i32 constant out of range " + + "unknown label"; + public static TestModule of(File file, ModuleType moduleType) { if (moduleType == ModuleType.TEXT) { - throw new UnsupportedOperationException( - "Parsing of textual WASM sources is not implemented yet."); + byte[] parsed; + try { + parsed = Wat2Wasm.parse(file); + } catch (Exception e) { + throw new MalformedException( + e.getMessage() + HACK_MATCH_ALL_MALFORMED_EXCEPTION_TEXT); + } + return of(Module.builder(parsed)); } - return of(file); + return of(Module.builder(file)); } - public TestModule(File file) { - this.file = file; + public TestModule(Module.Builder builder) { + this.builder = builder; } public TestModule build() { if (this.module == null) { - this.module = Module.builder(file).build(); + this.module = builder.build(); } return this; } @@ -62,10 +96,6 @@ public TestModule instantiate() { return this; } - public File file() { - return file; - } - public Module module() { return module; } diff --git a/test-gen-plugin/src/main/java/com/dylibso/chicory/maven/JavaTestGen.java b/test-gen-plugin/src/main/java/com/dylibso/chicory/maven/JavaTestGen.java index 52337407f..eb6518a8d 100644 --- a/test-gen-plugin/src/main/java/com/dylibso/chicory/maven/JavaTestGen.java +++ b/test-gen-plugin/src/main/java/com/dylibso/chicory/maven/JavaTestGen.java @@ -490,12 +490,6 @@ private void generateAssertThrows( method.addAnnotation( new SingleMemberAnnotationExpr( new Name("Disabled"), new StringLiteralExpr("Test excluded"))); - } else if (cmd.moduleType() != null && cmd.moduleType().equalsIgnoreCase("text")) { - method.addAnnotation( - new SingleMemberAnnotationExpr( - new Name("Disabled"), - new StringLiteralExpr( - "Parsing of textual WASM sources is not implemented yet"))); } } diff --git a/wasm-testsuite/pom.xml b/wasm-testsuite/pom.xml index 185346420..81bf51d62 100644 --- a/wasm-testsuite/pom.xml +++ b/wasm-testsuite/pom.xml @@ -14,6 +14,28 @@ WebAssembly Test Suite + + com.dylibso.chicory + wat2wasm + + + com.dylibso.chicory + wasm + + + com.dylibso.chicory + log + + + com.dylibso.chicory + runtime + + + com.dylibso.chicory + wasi + + + org.junit.jupiter junit-jupiter-engine diff --git a/wat2wasm/pom.xml b/wat2wasm/pom.xml new file mode 100644 index 000000000..ba542c741 --- /dev/null +++ b/wat2wasm/pom.xml @@ -0,0 +1,94 @@ + + + 4.0.0 + + + com.dylibso.chicory + chicory + 999-SNAPSHOT + + wat2wasm + jar + + Chicory - wat2wasm + wat2wasm running in pure Java with shaded Chicory + + + + 0.0.10 + + + + + com.dylibso.chicory + wasi + ${chicory.latest.version} + compile + + + com.dylibso.chicory + runtime + ${chicory.latest.version} + compile + + + com.dylibso.chicory + log + ${chicory.latest.version} + compile + + + com.dylibso.chicory + wasm + ${chicory.latest.version} + compile + + + com.google.jimfs + jimfs + + + org.junit.jupiter + junit-jupiter-engine + + + + + + + org.apache.maven.plugins + maven-shade-plugin + + + + shade + + package + + shaded + true + + + com.dylibso.chicory.log + shaded.com.dylibso.chicory.log + + + com.dylibso.chicory.runtime + shaded.com.dylibso.chicory.runtime + + + com.dylibso.chicory.wasm + shaded.com.dylibso.chicory.wasm + + + com.dylibso.chicory.wasi + shaded.com.dylibso.chicory.wasi + + + + + + + + + diff --git a/wat2wasm/src/main/java/com/dylibso/chicory/wat2wasm/Wat2Wasm.java b/wat2wasm/src/main/java/com/dylibso/chicory/wat2wasm/Wat2Wasm.java new file mode 100644 index 000000000..e0147a94f --- /dev/null +++ b/wat2wasm/src/main/java/com/dylibso/chicory/wat2wasm/Wat2Wasm.java @@ -0,0 +1,73 @@ +package com.dylibso.chicory.wat2wasm; + +import static java.nio.file.Files.copy; + +import com.dylibso.chicory.log.Logger; +import com.dylibso.chicory.log.SystemLogger; +import com.dylibso.chicory.runtime.HostImports; +import com.dylibso.chicory.runtime.Module; +import com.dylibso.chicory.runtime.exceptions.WASMMachineException; +import com.dylibso.chicory.wasi.WasiExitException; +import com.dylibso.chicory.wasi.WasiOptions; +import com.dylibso.chicory.wasi.WasiPreview1; +import com.dylibso.chicory.wasm.exceptions.MalformedException; +import com.google.common.jimfs.Configuration; +import com.google.common.jimfs.Jimfs; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.List; + +public class Wat2Wasm { + private static final Logger logger = new SystemLogger(); + private static final Module module = Module.builder("wat2wasm").build(); + + public static byte[] parse(File file) { + try (ByteArrayOutputStream stdoutStream = new ByteArrayOutputStream(); + ByteArrayOutputStream stderrStream = new ByteArrayOutputStream()) { + try (FileInputStream fis = new FileInputStream(file); + FileSystem fs = + Jimfs.newFileSystem( + Configuration.unix().toBuilder() + .setAttributeViews("unix") + .build())) { + + var wasiOpts = WasiOptions.builder(); + + wasiOpts.withStdout(stdoutStream); + wasiOpts.withStderr(stdoutStream); + + Path target = fs.getPath("tmp"); + java.nio.file.Files.createDirectory(target); + Path path = target.resolve("file.wat"); + copy(fis, path, StandardCopyOption.REPLACE_EXISTING); + wasiOpts.withDirectory(target.toString(), target); + + wasiOpts.withArguments(List.of("wat2wasm", path.toString(), "--output=-")); + + var wasi = new WasiPreview1(logger, wasiOpts.build()); + var imports = new HostImports(wasi.toHostFunctions()); + + module.withHostImports(imports).instantiate(); + + return stdoutStream.toByteArray(); + } catch (WASMMachineException e) { + assert (e.getCause() instanceof WasiExitException); + var stdout = new String(stdoutStream.toByteArray()); + var stderr = new String(stderrStream.toByteArray()); + throw new MalformedException(stdout + "\n" + stderr); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/wat2wasm/src/main/resources/wat2wasm b/wat2wasm/src/main/resources/wat2wasm new file mode 100755 index 000000000..2d19cb952 Binary files /dev/null and b/wat2wasm/src/main/resources/wat2wasm differ diff --git a/wat2wasm/src/test/java/com/dylibso/chicory/wat2wasm/Wat2WasmTest.java b/wat2wasm/src/test/java/com/dylibso/chicory/wat2wasm/Wat2WasmTest.java new file mode 100644 index 000000000..387ea54ca --- /dev/null +++ b/wat2wasm/src/test/java/com/dylibso/chicory/wat2wasm/Wat2WasmTest.java @@ -0,0 +1,32 @@ +package com.dylibso.chicory.wat2wasm; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.dylibso.chicory.wasm.exceptions.MalformedException; +import java.io.File; +import org.junit.jupiter.api.Test; + +public class Wat2WasmTest { + + @Test + public void shouldRunWat2Wasm() throws Exception { + var result = Wat2Wasm.parse(new File("../wasm-corpus/src/test/resources/wat/iterfact.wat")); + + assertTrue(result.length > 0); + assertTrue(new String(result).contains("iterFact")); + } + + @Test + public void shouldThrowMalformedException() throws Exception { + var malformedException = + assertThrows( + MalformedException.class, + () -> + Wat2Wasm.parse( + new File( + "src/test/resources/utf8-invalid-encoding-spec.0.wat"))); + + assertTrue(malformedException.getMessage().contains("invalid utf-8 encoding")); + } +} diff --git a/wat2wasm/src/test/resources/utf8-invalid-encoding-spec.0.wat b/wat2wasm/src/test/resources/utf8-invalid-encoding-spec.0.wat new file mode 100644 index 000000000..424f40b4d --- /dev/null +++ b/wat2wasm/src/test/resources/utf8-invalid-encoding-spec.0.wat @@ -0,0 +1 @@ +(func (export "\00\00\fe\ff")) \ No newline at end of file