diff --git a/runtime/pom.xml b/runtime/pom.xml index ffa89ba09..6fe81e7de 100644 --- a/runtime/pom.xml +++ b/runtime/pom.xml @@ -39,6 +39,9 @@ + + i32.wast,i64.wast,local_get.wast,memory.wast,return.wast + diff --git a/runtime/src/main/java/com/dylibso/chicory/runtime/ChicoryException.java b/runtime/src/main/java/com/dylibso/chicory/runtime/ChicoryException.java deleted file mode 100644 index bad25a99e..000000000 --- a/runtime/src/main/java/com/dylibso/chicory/runtime/ChicoryException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.dylibso.chicory.runtime; - -public class ChicoryException extends RuntimeException { - public ChicoryException(String msg) { - super(msg); - } -} diff --git a/runtime/src/main/java/com/dylibso/chicory/runtime/ExportFunction.java b/runtime/src/main/java/com/dylibso/chicory/runtime/ExportFunction.java index 0d9343bad..53deed614 100644 --- a/runtime/src/main/java/com/dylibso/chicory/runtime/ExportFunction.java +++ b/runtime/src/main/java/com/dylibso/chicory/runtime/ExportFunction.java @@ -1,5 +1,6 @@ package com.dylibso.chicory.runtime; +import com.dylibso.chicory.runtime.exceptions.ChicoryException; import com.dylibso.chicory.wasm.types.Value; /** diff --git a/runtime/src/main/java/com/dylibso/chicory/runtime/Machine.java b/runtime/src/main/java/com/dylibso/chicory/runtime/Machine.java index 8d60d92ec..4ab132a88 100644 --- a/runtime/src/main/java/com/dylibso/chicory/runtime/Machine.java +++ b/runtime/src/main/java/com/dylibso/chicory/runtime/Machine.java @@ -1,5 +1,6 @@ package com.dylibso.chicory.runtime; +import com.dylibso.chicory.runtime.exceptions.ChicoryException; import com.dylibso.chicory.wasm.types.*; import java.util.*; 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 cfb052d19..f0a184390 100644 --- a/runtime/src/main/java/com/dylibso/chicory/runtime/Module.java +++ b/runtime/src/main/java/com/dylibso/chicory/runtime/Module.java @@ -1,10 +1,11 @@ package com.dylibso.chicory.runtime; +import com.dylibso.chicory.runtime.exceptions.ChicoryException; +import com.dylibso.chicory.runtime.exceptions.InvalidException; import com.dylibso.chicory.wasm.Parser; import com.dylibso.chicory.wasm.types.*; import java.util.HashMap; -import java.util.Map; public class Module { private com.dylibso.chicory.wasm.Module module; @@ -15,6 +16,16 @@ public static Module build(String wasmFile) { return new Module(parser.parseModule()); } + public static Module build(String wasmFile, ModuleType type) { + switch (type) { + case TEXT: + return build(wasmFile); + default: + // TODO: implement me + throw new InvalidException("type mismatch"); + } + } + protected Module(com.dylibso.chicory.wasm.Module module) { this.module = module; this.exports = new HashMap<>(); diff --git a/runtime/src/main/java/com/dylibso/chicory/runtime/ModuleType.java b/runtime/src/main/java/com/dylibso/chicory/runtime/ModuleType.java new file mode 100644 index 000000000..3d69ad03c --- /dev/null +++ b/runtime/src/main/java/com/dylibso/chicory/runtime/ModuleType.java @@ -0,0 +1,6 @@ +package com.dylibso.chicory.runtime; + +public enum ModuleType { + TEXT, + BINARY +} diff --git a/runtime/src/main/java/com/dylibso/chicory/runtime/TrapException.java b/runtime/src/main/java/com/dylibso/chicory/runtime/TrapException.java index b70e27a6b..8b1199b59 100644 --- a/runtime/src/main/java/com/dylibso/chicory/runtime/TrapException.java +++ b/runtime/src/main/java/com/dylibso/chicory/runtime/TrapException.java @@ -1,5 +1,7 @@ package com.dylibso.chicory.runtime; +import com.dylibso.chicory.runtime.exceptions.ChicoryException; + import java.util.List; import java.util.Stack; diff --git a/runtime/src/main/java/com/dylibso/chicory/runtime/exceptions/ChicoryException.java b/runtime/src/main/java/com/dylibso/chicory/runtime/exceptions/ChicoryException.java new file mode 100644 index 000000000..76a07f41a --- /dev/null +++ b/runtime/src/main/java/com/dylibso/chicory/runtime/exceptions/ChicoryException.java @@ -0,0 +1,13 @@ +package com.dylibso.chicory.runtime.exceptions; + +public class ChicoryException extends RuntimeException { + public ChicoryException(String msg) { + super(msg); + } + public ChicoryException(Throwable cause) { + super(cause); + } + public ChicoryException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/runtime/src/main/java/com/dylibso/chicory/runtime/exceptions/InvalidException.java b/runtime/src/main/java/com/dylibso/chicory/runtime/exceptions/InvalidException.java new file mode 100644 index 000000000..60860408a --- /dev/null +++ b/runtime/src/main/java/com/dylibso/chicory/runtime/exceptions/InvalidException.java @@ -0,0 +1,13 @@ +package com.dylibso.chicory.runtime.exceptions; + +public class InvalidException extends ChicoryException { + public InvalidException(String msg) { + super(msg); + } + public InvalidException(Throwable cause) { + super(cause); + } + public InvalidException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/runtime/src/main/java/com/dylibso/chicory/runtime/exceptions/MalformedException.java b/runtime/src/main/java/com/dylibso/chicory/runtime/exceptions/MalformedException.java new file mode 100644 index 000000000..4cc28056a --- /dev/null +++ b/runtime/src/main/java/com/dylibso/chicory/runtime/exceptions/MalformedException.java @@ -0,0 +1,13 @@ +package com.dylibso.chicory.runtime.exceptions; + +public class MalformedException extends ChicoryException { + public MalformedException(String msg) { + super(msg); + } + public MalformedException(Throwable cause) { + super(cause); + } + public MalformedException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/runtime/src/main/java/com/dylibso/chicory/runtime/exceptions/WASMRuntimeException.java b/runtime/src/main/java/com/dylibso/chicory/runtime/exceptions/WASMRuntimeException.java new file mode 100644 index 000000000..eb0ea3df2 --- /dev/null +++ b/runtime/src/main/java/com/dylibso/chicory/runtime/exceptions/WASMRuntimeException.java @@ -0,0 +1,13 @@ +package com.dylibso.chicory.runtime.exceptions; + +public class WASMRuntimeException extends ChicoryException { + public WASMRuntimeException(String msg) { + super(msg); + } + public WASMRuntimeException(Throwable cause) { + super(cause); + } + public WASMRuntimeException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/runtime/src/test/java/com/dylibso/chicory/runtime/SpecV1I32Test.java b/runtime/src/test/java/com/dylibso/chicory/runtime/SpecV1I32Test.java_old similarity index 100% rename from runtime/src/test/java/com/dylibso/chicory/runtime/SpecV1I32Test.java rename to runtime/src/test/java/com/dylibso/chicory/runtime/SpecV1I32Test.java_old diff --git a/runtime/src/test/java/com/dylibso/chicory/runtime/SpecV1I64Test.java b/runtime/src/test/java/com/dylibso/chicory/runtime/SpecV1I64Test.java_old similarity index 100% rename from runtime/src/test/java/com/dylibso/chicory/runtime/SpecV1I64Test.java rename to runtime/src/test/java/com/dylibso/chicory/runtime/SpecV1I64Test.java_old diff --git a/runtime/src/test/java/com/dylibso/chicory/runtime/SpecV1LocalGetTest.java b/runtime/src/test/java/com/dylibso/chicory/runtime/SpecV1LocalGetTest.java_old similarity index 100% rename from runtime/src/test/java/com/dylibso/chicory/runtime/SpecV1LocalGetTest.java rename to runtime/src/test/java/com/dylibso/chicory/runtime/SpecV1LocalGetTest.java_old diff --git a/runtime/src/test/java/com/dylibso/chicory/runtime/SpecV1MemoryTest.java b/runtime/src/test/java/com/dylibso/chicory/runtime/SpecV1MemoryTest.java_old similarity index 100% rename from runtime/src/test/java/com/dylibso/chicory/runtime/SpecV1MemoryTest.java rename to runtime/src/test/java/com/dylibso/chicory/runtime/SpecV1MemoryTest.java_old diff --git a/runtime/src/test/java/com/dylibso/chicory/runtime/SpecV1ReturnTest.java b/runtime/src/test/java/com/dylibso/chicory/runtime/SpecV1ReturnTest.java_old similarity index 100% rename from runtime/src/test/java/com/dylibso/chicory/runtime/SpecV1ReturnTest.java rename to runtime/src/test/java/com/dylibso/chicory/runtime/SpecV1ReturnTest.java_old diff --git a/test-gen-plugin/src/main/java/com/dylibso/chicory/maven/TestGenMojo.java b/test-gen-plugin/src/main/java/com/dylibso/chicory/maven/TestGenMojo.java index b47dc1693..b2b6eff9b 100644 --- a/test-gen-plugin/src/main/java/com/dylibso/chicory/maven/TestGenMojo.java +++ b/test-gen-plugin/src/main/java/com/dylibso/chicory/maven/TestGenMojo.java @@ -3,25 +3,17 @@ import com.dylibso.chicory.maven.wast.Command; import com.dylibso.chicory.maven.wast.CommandType; import com.dylibso.chicory.maven.wast.Wast; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.Modifier; -import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; import com.github.javaparser.ast.body.MethodDeclaration; -import com.github.javaparser.ast.expr.AssignExpr; +import com.github.javaparser.ast.body.VariableDeclarator; import com.github.javaparser.ast.expr.Expression; import com.github.javaparser.ast.expr.NameExpr; -import com.github.javaparser.ast.expr.TypeExpr; import com.github.javaparser.ast.expr.VariableDeclarationExpr; -import com.github.javaparser.ast.stmt.AssertStmt; -import com.github.javaparser.ast.stmt.BlockStmt; -import com.github.javaparser.ast.stmt.EmptyStmt; -import com.github.javaparser.ast.stmt.ExpressionStmt; -import com.github.javaparser.ast.stmt.Statement; import com.github.javaparser.ast.type.ClassOrInterfaceType; -import com.github.javaparser.ast.type.Type; import com.github.javaparser.utils.SourceRoot; +import com.github.javaparser.utils.StringEscapeUtils; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.logging.Log; @@ -40,8 +32,10 @@ import java.net.URL; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; +import java.util.Arrays; import java.util.List; import java.util.Locale; +import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -85,7 +79,7 @@ public class TestGenMojo extends AbstractMojo { @Parameter(required = true, defaultValue = "${project.build.directory}/wabt") private File downloadTargetFolder; - @Parameter(required = true, defaultValue = "return.wast") + @Parameter(required = true) private List wastToProcess; /** @@ -216,6 +210,8 @@ private File executeWast2Json(String wast2jsonBinary, File wastFile) { return targetFolder; } + private String INSTANCE_NAME = "instance"; + private void generateJava(File specFile, File targetFolder) { Wast wast; try { @@ -227,43 +223,61 @@ private void generateJava(File specFile, File targetFolder) { final SourceRoot dest = new SourceRoot(sourceDestinationFolder.toPath()); var cu = new CompilationUnit("com.dylibso.chicory.test.gen"); var name = specFile.toPath().getParent().toFile().getName(); - var testName = name.substring(0, 1).toUpperCase() + name.substring(1) + "Test"; + var testName = "SpecV1" + capitalize(escapedCamelCase(name)) + "Test"; cu.setStorage(sourceDestinationFolder.toPath().resolve(testName + ".java")); - // default imports + // all the imports cu.addImport("org.junit.Test"); + + cu.addImport("com.dylibso.chicory.runtime.exceptions.InvalidException"); + cu.addImport("com.dylibso.chicory.runtime.exceptions.MalformedException"); + cu.addImport("com.dylibso.chicory.runtime.exceptions.WASMRuntimeException"); + cu.addImport("com.dylibso.chicory.runtime.ExportFunction"); cu.addImport("com.dylibso.chicory.runtime.Instance"); cu.addImport("com.dylibso.chicory.runtime.Module"); + cu.addImport("com.dylibso.chicory.runtime.ModuleType"); + + cu.addImport("com.dylibso.chicory.wasm.types.Value"); + cu.addImport("org.junit.Assert.assertEquals", true, false); + cu.addImport("org.junit.Assert.assertThrows", true, false); var testClass = cu.addClass(testName); - Expression moduleInstantiation = null; MethodDeclaration method = null; int testNumber = 0; + int moduleInstantiationNumber = 0; for (var cmd: wast.getCommands()) { - log.error("****"); - log.error(cmd.toString()); switch (cmd.getType()) { case MODULE: - moduleInstantiation = generateModuleInstantiation(cmd, targetFolder); - method = testClass.addMethod("test" + testNumber++, Modifier.Keyword.PUBLIC); - method.addAnnotation("Test"); + testClass.addFieldWithInitializer( + new ClassOrInterfaceType("Instance"), + INSTANCE_NAME + moduleInstantiationNumber++, + generateModuleInstantiation(cmd, targetFolder)); break; case ASSERT_RETURN: - if (moduleInstantiation != null) { - // initialize - method.setBody(new BlockStmt().addStatement( - new AssignExpr( - new VariableDeclarationExpr( - new ClassOrInterfaceType("Instance"), "instance"), moduleInstantiation, AssignExpr.Operator.ASSIGN))); - moduleInstantiation = null; + case ASSERT_TRAP: + method = testClass.addMethod("test" + testNumber++, Modifier.Keyword.PUBLIC); + method.addAnnotation("Test"); + + var varName = escapedCamelCase(cmd.getAction().getField()); + var fieldExport = generateFieldExport(varName, cmd, (moduleInstantiationNumber - 1)); + if (fieldExport.isPresent()) { + method.getBody().get().addStatement(fieldExport.get()); } - method.getBody().get().addStatement(generateAssert(cmd)); + for (var expr: generateAssert(varName, cmd)) { + method.getBody().get().addStatement(expr); + } break; case ASSERT_INVALID: - // TODO: generate assertThrows + case ASSERT_MALFORMED: + method = testClass.addMethod("test" + testNumber++, Modifier.Keyword.PUBLIC); + method.addAnnotation("Test"); + + for (var expr: generateAssertThrows(cmd, targetFolder)) { + method.getBody().get().addStatement(expr); + } break; } } @@ -272,28 +286,86 @@ private void generateJava(File specFile, File targetFolder) { dest.saveAll(); } - private Statement generateAssert(Command cmd) { - assert(cmd.getType() == CommandType.ASSERT_RETURN); - // TODO: implement me + private String capitalize(String in) { + return in.substring(0, 1).toUpperCase() + in.substring(1); + } + + private String escapedCamelCase(String in) { + var escaped = StringEscapeUtils.escapeJava(in); + var sb = new StringBuffer(); + var capitalize = false; + for (var i = 0; i < escaped.length(); i++) { + var character = escaped.charAt(i); + + if (Character.isDigit(character)) { + sb.append(character); + } else if (Character.isAlphabetic(character)) { + if (capitalize) { + sb.append(Character.toUpperCase(character)); + capitalize = false; + } else { + sb.append(character); + } + } else { + capitalize = true; + } + } + + return sb.toString(); + } + + private Optional generateFieldExport(String varName, Command cmd, int instanceNumber) { + if (cmd.getAction() != null && cmd.getAction().getField() != null) { + var declarator = new VariableDeclarator() + .setName(varName) + .setType(new ClassOrInterfaceType("ExportFunction")) + .setInitializer(new NameExpr(INSTANCE_NAME + instanceNumber + ".getExport(\"" + cmd.getAction().getField() + "\")")); + Expression varDecl = new VariableDeclarationExpr(declarator); + return Optional.of(varDecl); + } else { + return Optional.empty(); + } + } + + private List generateAssert(String varName, Command cmd) { + assert(cmd.getType() == CommandType.ASSERT_RETURN || cmd.getType() == CommandType.ASSERT_TRAP); var returnVar = "null"; + var typeConversion = ""; + var deltaParam = ""; if (cmd.getExpected() != null && cmd.getExpected().length > 0) { - // TODO: emit the correct value if (cmd.getExpected().length == 1) { - var value = cmd.getExpected()[0]; - // TODO: go on from here unnwrapping primitive types + var expected = cmd.getExpected()[0]; + returnVar = expected.toJavaValue(); + typeConversion = expected.extractType(); + deltaParam = expected.getDelta(); } else { - throw new RuntimeException("Not implemented yet"); + throw new RuntimeException("Multiple expected return, implement me!"); } } String invocationMethod = null; switch (cmd.getAction().getType()) { case INVOKE: - invocationMethod = ".apply()"; + var args = Arrays.stream(cmd.getAction().getArgs()).map(arg -> arg.toWasmValue()).collect(Collectors.joining(", ")); + invocationMethod = ".apply(" + args + ")"; break; } - return new ExpressionStmt(new NameExpr("assertEquals(" + returnVar + ", instance.getExport(\"" + cmd.getAction().getField() +"\")" + invocationMethod + ")")); + + switch (cmd.getType()) { + case ASSERT_RETURN: + return List.of(new NameExpr("assertEquals(" + returnVar + ", "+ varName + invocationMethod + typeConversion + deltaParam + ")")); + case ASSERT_TRAP: + var assertDecl = new NameExpr("var exception = assertThrows(WASMRuntimeException.class, () -> "+ varName + invocationMethod + typeConversion + ")"); + if (cmd.getText() != null) { + var messageMatch = new NameExpr("assertEquals(\"" + cmd.getText() + "\", exception.getMessage())"); + return List.of(assertDecl, messageMatch); + } else { + return List.of(assertDecl); + } + } + + throw new RuntimeException("Unreachable"); } private Expression generateModuleInstantiation(Command cmd, File folder) { @@ -302,7 +374,33 @@ private Expression generateModuleInstantiation(Command cmd, File folder) { var relativeFile = folder.toPath().resolve(cmd.getFilename()).toFile().getAbsolutePath() .replaceFirst(project.getBasedir().getAbsolutePath() + "/", ""); - return new NameExpr("Module.build(\"" + relativeFile + "\").instantiate()"); + var additionalParam = ""; + if (cmd.getModuleType() != null) { + additionalParam = ", ModuleType." + cmd.getModuleType().toUpperCase(); + } + return new NameExpr("Module.build(\"" + relativeFile + "\"" + additionalParam + ").instantiate()"); + } + + private List generateAssertThrows(Command cmd, File targetFolder) { + assert(cmd.getType() == CommandType.ASSERT_INVALID || cmd.getType() == CommandType.ASSERT_MALFORMED); + + var exceptionType = ""; + if (cmd.getType() == CommandType.ASSERT_INVALID) { + exceptionType = "InvalidException"; + } else if (cmd.getType() == CommandType.ASSERT_MALFORMED) { + exceptionType = "MalformedException"; + } + + var assignementStmt = (cmd.getText() != null) ? "var exception = " : ""; + + var assertThrows = new NameExpr(assignementStmt + "assertThrows(" + exceptionType + ".class, () -> " + generateModuleInstantiation(cmd, targetFolder) + ")"); + + if (cmd.getText() != null) { + var messageMatch = new NameExpr("assertEquals(\"" + cmd.getText() + "\", exception.getMessage())"); + return List.of(assertThrows, messageMatch); + } else { + return List.of(assertThrows); + } } private void generateTests(File testsuiteFolder, File sourceDestinationFolder) throws Exception { @@ -312,6 +410,10 @@ private void generateTests(File testsuiteFolder, File sourceDestinationFolder) t sourceDestinationFolder.mkdirs(); for (var spec: wastToProcess) { + var wastFile = testsuiteFolder.toPath().resolve(spec).toFile(); + if (!wastFile.exists()) { + throw new IllegalArgumentException("Wast file " + wastFile.getAbsolutePath() + " not found"); + } var destFolder = executeWast2Json(wast2JsonBinary, testsuiteFolder.toPath().resolve(spec).toFile()); generateJava(destFolder.toPath().resolve(SPEC_JSON).toFile(), destFolder); } diff --git a/test-gen-plugin/src/main/java/com/dylibso/chicory/maven/wast/CommandType.java b/test-gen-plugin/src/main/java/com/dylibso/chicory/maven/wast/CommandType.java index 4b95b8c21..404a39e5a 100644 --- a/test-gen-plugin/src/main/java/com/dylibso/chicory/maven/wast/CommandType.java +++ b/test-gen-plugin/src/main/java/com/dylibso/chicory/maven/wast/CommandType.java @@ -8,9 +8,12 @@ public enum CommandType { MODULE("module"), @JsonProperty("assert_return") ASSERT_RETURN("assert_return"), - + @JsonProperty("assert_trap") + ASSERT_TRAP("assert_trap"), @JsonProperty("assert_invalid") - ASSERT_INVALID("assert_invalid"); + ASSERT_INVALID("assert_invalid"), + @JsonProperty("assert_malformed") + ASSERT_MALFORMED("assert_malformed"); private String value; CommandType(String value) { diff --git a/test-gen-plugin/src/main/java/com/dylibso/chicory/maven/wast/WasmValue.java b/test-gen-plugin/src/main/java/com/dylibso/chicory/maven/wast/WasmValue.java index 88d98b9ff..41240cbe4 100644 --- a/test-gen-plugin/src/main/java/com/dylibso/chicory/maven/wast/WasmValue.java +++ b/test-gen-plugin/src/main/java/com/dylibso/chicory/maven/wast/WasmValue.java @@ -17,4 +17,55 @@ public WasmValueType getType() { public String getValue() { return value; } + + public String toJavaValue() { + switch (type) { + case I32: return "Integer.parseUnsignedInt(\"" + value + "\")"; + case I64: return "Long.parseUnsignedLong(\"" + value + "\")"; + case F32: return "Float.parseFloat(\"" + value + "\")"; + case F64: return "Double.parseDouble(\"" + value + "\")"; + default: + throw new IllegalArgumentException("Type not recognized " + type); + } + } + + public String toWasmValue() { + switch (type) { + case I32: return "Value.i32(Integer.parseUnsignedInt(\"" + value + "\"))"; + case I64: return "Value.i64(Long.parseUnsignedLong(\"" + value + "\"))"; + case F32: return "Value.fromFloat(Float.parseFloat(\"" + value + "\"))"; + case F64: return "Value.fromDouble(Double.parseDouble(\"" + value + "\"))"; + default: + throw new IllegalArgumentException("Type not recognized " + type); + } + } + + public String extractType() { + if (value == null || value.equals("null")) { + return ""; + } else { + switch (type) { + case I32: + return ".asInt()"; + case I64: + return ".asLong()"; + case F32: + return ".asFloat()"; + case F64: + return ".asDouble()"; + default: + throw new IllegalArgumentException("Type not recognized " + type); + } + } + } + + public String getDelta() { + switch (type) { + case F32: + case F64: + return ", 0.0"; + default: + return ""; + } + } }