diff --git a/bom/pom.xml b/bom/pom.xml index af3f71462..afe9217ed 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -78,6 +78,11 @@ redline-bridge-experimental ${project.version} + + run.endive + redline-build-time-compiler-experimental + ${project.version} + run.endive redline-compiler-experimental diff --git a/build-time-compiler-cli/src/main/java/run/endive/experimental/compiler/cli/Cli.java b/build-time-compiler-cli/src/main/java/run/endive/experimental/compiler/cli/Cli.java index 9e4245caa..6a954e005 100644 --- a/build-time-compiler-cli/src/main/java/run/endive/experimental/compiler/cli/Cli.java +++ b/build-time-compiler-cli/src/main/java/run/endive/experimental/compiler/cli/Cli.java @@ -71,6 +71,14 @@ public String[] getVersion() { "The indexes of functions that should be interpreted, separated by commas") Set interpretedFunctions; + @CommandLine.Option( + order = 7, + names = "--module-interface", + description = + "Fully qualified class name for which to generate _ModuleExports and" + + " _ModuleImports wrappers") + String moduleInterface; + @Override public void run() { var config = @@ -82,6 +90,7 @@ public void run() { .withTargetWasmFolder(targetWasmFolder) .withInterpreterFallback(interpreterFallback) .withInterpretedFunctions(interpretedFunctions) + .withModuleInterface(moduleInterface) .build(); var generator = new Generator(config); @@ -90,13 +99,19 @@ public void run() { var interpretedFunctions = generator.generateResources(); generator.generateMetaWasm(interpretedFunctions); generator.generateSources(); + if (moduleInterface != null && !moduleInterface.isEmpty()) { + generator.generateModuleInterface(moduleInterface); + } } catch (IOException e) { throw new CommandLine.PicocliException("Failed to execute the command", e); } } + public static int execute(String[] args) { + return new CommandLine(new Cli()).execute(args); + } + public static void main(String[] args) { - int exitCode = new CommandLine(new Cli()).execute(args); - System.exit(exitCode); + System.exit(execute(args)); } } diff --git a/build-time-compiler/src/main/java/run/endive/build/time/compiler/Config.java b/build-time-compiler/src/main/java/run/endive/build/time/compiler/Config.java index 1aba2b9b9..22a870d6e 100644 --- a/build-time-compiler/src/main/java/run/endive/build/time/compiler/Config.java +++ b/build-time-compiler/src/main/java/run/endive/build/time/compiler/Config.java @@ -1,6 +1,7 @@ package run.endive.build.time.compiler; import java.nio.file.Path; +import java.util.List; import java.util.Set; import java.util.StringJoiner; import run.endive.compiler.InterpreterFallback; @@ -46,6 +47,16 @@ public final class Config { */ private final String moduleInterface; + /** + * target triples for redline native compilation (empty = no native compilation) + */ + private final List redlineTargets; + + /** + * the target resource folder for native code files + */ + private final Path targetResourceFolder; + private Config( Path wasmFile, String name, @@ -54,7 +65,9 @@ private Config( Path targetWasmFolder, InterpreterFallback interpreterFallback, Set interpretedFunctions, - String moduleInterface) { + String moduleInterface, + List redlineTargets, + Path targetResourceFolder) { this.wasmFile = wasmFile; this.name = name; this.targetClassFolder = targetClassFolder; @@ -63,6 +76,8 @@ private Config( this.interpreterFallback = interpreterFallback; this.interpretedFunctions = interpretedFunctions; this.moduleInterface = moduleInterface; + this.redlineTargets = redlineTargets; + this.targetResourceFolder = targetResourceFolder; } public Path wasmFile() { @@ -97,6 +112,18 @@ public String moduleInterface() { return moduleInterface; } + public List redlineTargets() { + return redlineTargets; + } + + public Path targetResourceFolder() { + return targetResourceFolder; + } + + public boolean hasRedlineTargets() { + return redlineTargets != null && !redlineTargets.isEmpty(); + } + public static Builder builder() { return new Builder(); } @@ -126,6 +153,8 @@ public static final class Builder { private InterpreterFallback interpreterFallback = InterpreterFallback.FAIL; private Set interpretedFunctions; private String moduleInterface; + private List redlineTargets = List.of(); + private Path targetResourceFolder; private Builder() {} @@ -169,6 +198,16 @@ public Builder withModuleInterface(String moduleInterface) { return this; } + public Builder withRedlineTargets(List redlineTargets) { + this.redlineTargets = redlineTargets; + return this; + } + + public Builder withTargetResourceFolder(Path targetResourceFolder) { + this.targetResourceFolder = targetResourceFolder; + return this; + } + public Config build() { return new Config( wasmFile, @@ -178,7 +217,9 @@ public Config build() { targetWasmFolder, interpreterFallback, interpretedFunctions, - moduleInterface); + moduleInterface, + redlineTargets, + targetResourceFolder); } } } diff --git a/build-time-compiler/src/main/java/run/endive/build/time/compiler/GeneratorMain.java b/build-time-compiler/src/main/java/run/endive/build/time/compiler/GeneratorMain.java new file mode 100644 index 000000000..fa24c8d3d --- /dev/null +++ b/build-time-compiler/src/main/java/run/endive/build/time/compiler/GeneratorMain.java @@ -0,0 +1,40 @@ +package run.endive.build.time.compiler; + +import java.io.IOException; +import java.nio.file.Path; +import run.endive.compiler.InterpreterFallback; + +public final class GeneratorMain { + + private GeneratorMain() {} + + public static void main(String[] args) throws IOException { + if (args.length < 5) { + throw new IllegalArgumentException( + "Usage: GeneratorMain " + + " " + + " [interpreterFallback] [moduleInterface]"); + } + var configBuilder = + Config.builder() + .withWasmFile(Path.of(args[0])) + .withName(args[1]) + .withTargetClassFolder(Path.of(args[2])) + .withTargetSourceFolder(Path.of(args[3])) + .withTargetWasmFolder(Path.of(args[4])); + if (args.length > 5 && !args[5].isEmpty()) { + configBuilder.withInterpreterFallback(InterpreterFallback.valueOf(args[5])); + } + if (args.length > 6 && !args[6].isEmpty()) { + configBuilder.withModuleInterface(args[6]); + } + var config = configBuilder.build(); + var generator = new Generator(config); + var interpreted = generator.generateResources(); + generator.generateMetaWasm(interpreted); + generator.generateSources(); + if (config.moduleInterface() != null && !config.moduleInterface().isEmpty()) { + generator.generateModuleInterface(config.moduleInterface()); + } + } +} diff --git a/compiler-maven-plugin/pom.xml b/compiler-maven-plugin/pom.xml index 223b8df00..303c703b7 100644 --- a/compiler-maven-plugin/pom.xml +++ b/compiler-maven-plugin/pom.xml @@ -62,4 +62,5 @@ + diff --git a/compiler-maven-plugin/src/main/java/run/endive/build/time/maven/EndiveCompilerGenMojo.java b/compiler-maven-plugin/src/main/java/run/endive/build/time/maven/EndiveCompilerGenMojo.java index 5866a32ed..21d30b5c9 100644 --- a/compiler-maven-plugin/src/main/java/run/endive/build/time/maven/EndiveCompilerGenMojo.java +++ b/compiler-maven-plugin/src/main/java/run/endive/build/time/maven/EndiveCompilerGenMojo.java @@ -2,6 +2,7 @@ import java.io.File; import java.io.IOException; +import java.util.List; import java.util.Set; import java.util.TreeSet; import org.apache.maven.model.Resource; @@ -21,65 +22,55 @@ @Mojo(name = "compile", defaultPhase = LifecyclePhase.GENERATE_SOURCES, threadSafe = true) public class EndiveCompilerGenMojo extends AbstractMojo { - /** - * the wasm module to be used - */ @Parameter(required = true) private File wasmFile; - /** - * the base name to be used for the generated classes - */ @Parameter(required = true) private String name; - /** - * the target folder to generate classes - */ @Parameter( required = true, defaultValue = "${project.build.directory}/generated-resources/endive-compiler") private File targetClassFolder; - /** - * the target source folder to generate the Machine implementation - */ @Parameter( required = true, defaultValue = "${project.build.directory}/generated-sources/endive-compiler") private File targetSourceFolder; - /** - * the target wasm folder to generate the stripped meta wasm module - */ @Parameter( required = true, defaultValue = "${project.build.directory}/generated-resources/endive-compiler") private File targetWasmFolder; - /** - * the action to take if the compiler needs to use the interpreter because a function is too big - */ @Parameter(required = true, defaultValue = "FAIL") InterpreterFallback interpreterFallback; - /** - * The indexes of functions that should be interpreted, separated by commas - */ @Parameter(required = false, defaultValue = "") Set interpretedFunctions; - /** - * Fully qualified name of the user's class that will use the compiled module. - * When set, the plugin generates _ModuleExports and _ModuleImports wrapper classes, - * eliminating the need for @WasmModuleInterface annotation and the annotation processor. - */ @Parameter(required = false) String moduleInterface; /** - * The current Maven project. + * Enable Redline native compilation (experimental) for all supported + * platforms (x86_64 and aarch64 on Linux, macOS, and Windows). + */ + @Parameter(required = false, defaultValue = "false") + boolean redlineExperimental; + + /** + * Target triples for Redline native compilation. Overrides {@code redlineExperimental} + * for fine-grained control over which platforms to cross-compile for. */ + @Parameter(required = false) + List redlineTargets; + + @Parameter( + required = true, + defaultValue = "${project.build.directory}/generated-resources/endive-compiler") + private File targetResourceFolder; + @Parameter(property = "project", required = true, readonly = true) private MavenProject project; @@ -87,7 +78,7 @@ public class EndiveCompilerGenMojo extends AbstractMojo { public void execute() throws MojoExecutionException { getLog().info("Compiling classes for " + name + " from " + wasmFile); - var config = + var configBuilder = Config.builder() .withWasmFile(wasmFile.toPath()) .withName(name) @@ -97,7 +88,13 @@ public void execute() throws MojoExecutionException { .withInterpreterFallback(interpreterFallback) .withInterpretedFunctions(interpretedFunctions) .withModuleInterface(moduleInterface) - .build(); + .withTargetResourceFolder(targetResourceFolder.toPath()); + if (redlineTargets != null && !redlineTargets.isEmpty()) { + configBuilder.withRedlineTargets(redlineTargets); + } else if (redlineExperimental) { + configBuilder.withRedlineTargets(redlineAllTargets()); + } + var config = configBuilder.build(); var generator = new Generator(config); @@ -106,6 +103,11 @@ public void execute() throws MojoExecutionException { generator.generateMetaWasm(finalInterpretedFunctions); generator.generateSources(); + if (config.hasRedlineTargets()) { + getLog().info("Redline native compilation for targets: " + config.redlineTargets()); + invokeRedlineGenerator(config); + } + if (moduleInterface != null && !moduleInterface.isEmpty()) { generator.generateModuleInterface(moduleInterface); } @@ -133,4 +135,35 @@ public void execute() throws MojoExecutionException { project.addResource(resource); project.addCompileSourceRoot(targetSourceFolder.getPath()); } + + @SuppressWarnings("unchecked") + private static List redlineAllTargets() throws MojoExecutionException { + try { + var clazz = Class.forName("run.endive.redline.experimental.build.RedlineGenerator"); + return (List) clazz.getMethod("allTargets").invoke(null); + } catch (ReflectiveOperationException e) { + throw new MojoExecutionException( + "Redline support requires redline-build-time-compiler on the classpath" + + " (build with -Predline)", + e); + } + } + + private static void invokeRedlineGenerator(Config config) throws IOException { + try { + var clazz = Class.forName("run.endive.redline.experimental.build.RedlineGenerator"); + var constructor = clazz.getConstructor(Config.class); + var generator = constructor.newInstance(config); + clazz.getMethod("generateNativeCode").invoke(generator); + clazz.getMethod("extendGeneratedSources").invoke(generator); + } catch (ReflectiveOperationException e) { + if (e.getCause() instanceof IOException) { + throw (IOException) e.getCause(); + } + throw new RuntimeException( + "Redline support requires redline-build-time-compiler on the classpath" + + " (build with -Predline)", + e); + } + } } diff --git a/pom.xml b/pom.xml index 0c33bf67e..44f6fc327 100644 --- a/pom.xml +++ b/pom.xml @@ -268,6 +268,11 @@ redline-bridge-experimental ${project.version} + + run.endive + redline-build-time-compiler-experimental + ${project.version} + run.endive redline-compiler-experimental diff --git a/redline/api/src/main/java/run/endive/redline/experimental/api/internal/RedlineTarget.java b/redline/api/src/main/java/run/endive/redline/experimental/api/internal/RedlineTarget.java index 933d2feef..dba0b0ab9 100644 --- a/redline/api/src/main/java/run/endive/redline/experimental/api/internal/RedlineTarget.java +++ b/redline/api/src/main/java/run/endive/redline/experimental/api/internal/RedlineTarget.java @@ -27,6 +27,15 @@ public String resourceSuffix() { return resourceSuffix; } + public static Optional fromTriple(String triple) { + for (RedlineTarget target : values()) { + if (target.triple.equals(triple)) { + return Optional.of(target); + } + } + return Optional.empty(); + } + public static Optional detectHost() { String osName = System.getProperty("endive.redline.os.name", System.getProperty("os.name", "")) diff --git a/redline/bridge/pom.xml b/redline/bridge/pom.xml index 412153b93..bbf6c3627 100644 --- a/redline/bridge/pom.xml +++ b/redline/bridge/pom.xml @@ -29,21 +29,76 @@ + - run.endive - endive-compiler-maven-plugin + org.codehaus.mojo + build-helper-maven-plugin + ${build-helper-maven-plugin.version} - redline-bridge + add-generated-sources - compile + add-source + generate-sources - run.endive.redline.experimental.bridge.internal.Cranelift - ${project.basedir}/../cranelift_bridge.wasm - WARN - run.endive.redline.experimental.bridge.internal.CraneliftBridge + + ${project.build.directory}/generated-sources/endive-compiler + + + + + add-generated-resources + + add-resource + + generate-resources + + + + ${project.build.directory}/generated-resources/endive-compiler + + + + + + + + org.codehaus.mojo + exec-maven-plugin + + + run.endive + build-time-compiler + ${project.version} + + + + + redline-bridge-compile + + java + + generate-sources + + run.endive.build.time.compiler.GeneratorMain + + ${project.basedir}/../cranelift_bridge.wasm + run.endive.redline.experimental.bridge.internal.Cranelift + ${project.build.directory}/generated-resources/endive-compiler + ${project.build.directory}/generated-sources/endive-compiler + ${project.build.directory}/generated-resources/endive-compiler + WARN + run.endive.redline.experimental.bridge.internal.CraneliftBridge + + true + false diff --git a/redline/build-time-compiler/pom.xml b/redline/build-time-compiler/pom.xml new file mode 100644 index 000000000..50ee68428 --- /dev/null +++ b/redline/build-time-compiler/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + + + run.endive + redline-parent-experimental + 999-SNAPSHOT + ../pom.xml + + redline-build-time-compiler-experimental + jar + Endive - Redline Build Time Compiler + Build-time native code generation via Cranelift + + + + com.github.javaparser + javaparser-core + + + run.endive + build-time-compiler + + + run.endive + redline-api-experimental + + + run.endive + redline-compiler-experimental + + + run.endive + wasm + + + diff --git a/redline/build-time-compiler/src/main/java/run/endive/redline/experimental/build/RedlineGenerator.java b/redline/build-time-compiler/src/main/java/run/endive/redline/experimental/build/RedlineGenerator.java new file mode 100644 index 000000000..9ebad5534 --- /dev/null +++ b/redline/build-time-compiler/src/main/java/run/endive/redline/experimental/build/RedlineGenerator.java @@ -0,0 +1,182 @@ +package run.endive.redline.experimental.build; + +import static com.github.javaparser.StaticJavaParser.parseClassOrInterfaceType; +import static com.github.javaparser.StaticJavaParser.parseType; + +import com.github.javaparser.StaticJavaParser; +import com.github.javaparser.ast.Modifier; +import com.github.javaparser.ast.NodeList; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.expr.FieldAccessExpr; +import com.github.javaparser.ast.expr.NameExpr; +import com.github.javaparser.ast.stmt.ReturnStmt; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import run.endive.build.time.compiler.Config; +import run.endive.redline.experimental.api.NativeCodeSerializer; +import run.endive.redline.experimental.api.internal.RedlineTarget; +import run.endive.redline.experimental.compiler.internal.NativeCompiler; +import run.endive.wasm.Parser; + +public final class RedlineGenerator { + + private final Config config; + + public static List allTargets() { + return Arrays.stream(RedlineTarget.values()) + .map(RedlineTarget::triple) + .collect(Collectors.toList()); + } + + public RedlineGenerator(Config config) { + this.config = config; + } + + public void generateNativeCode() throws IOException { + if (!config.hasRedlineTargets()) { + return; + } + var module = Parser.parse(config.wasmFile()); + var packagePath = config.getPackageName().replace('.', '/'); + var baseName = config.getBaseName(); + var resourceDir = config.targetResourceFolder().resolve(packagePath); + Files.createDirectories(resourceDir); + + for (String triple : config.redlineTargets()) { + byte[][] compiledCode = NativeCompiler.compileAll(triple, module); + + var target = + RedlineTarget.fromTriple(triple) + .orElseThrow( + () -> + new IllegalArgumentException( + "Unknown target triple: " + triple)); + var nativeFile = + resourceDir.resolve(baseName + "." + target.resourceSuffix() + ".native"); + + try (var out = new FileOutputStream(nativeFile.toFile())) { + NativeCodeSerializer.serialize(compiledCode, out); + } + } + } + + public void extendGeneratedSources() throws IOException { + if (!config.hasRedlineTargets()) { + return; + } + var packagePath = config.getPackageName().replace('.', '/'); + var baseName = config.getBaseName(); + var sourceFile = + config.targetSourceFolder().resolve(packagePath).resolve(baseName + ".java"); + + var cu = StaticJavaParser.parse(sourceFile); + var type = cu.getClassByName(baseName).orElseThrow(); + + cu.addImport("run.endive.redline.experimental.api.NativeCodeSerializer"); + cu.addImport("run.endive.redline.experimental.api.NativeMachineFactoryProvider"); + cu.addImport("run.endive.redline.experimental.api.internal.RedlineTarget"); + cu.addImport("java.io.InputStream"); + cu.addImport("java.io.IOException"); + cu.addImport("java.io.UncheckedIOException"); + cu.addImport("run.endive.runtime.Instance"); + + generateNativeCodeHolderInnerClass(type, baseName); + generateLoadNativeCodeMethod(type); + generateBuilderMethod(type, baseName); + generateSafeBuilderMethod(type, baseName); + + Files.writeString(sourceFile, cu.toString()); + } + + private static void generateNativeCodeHolderInnerClass( + ClassOrInterfaceDeclaration type, String moduleName) { + var holderClass = + new ClassOrInterfaceDeclaration( + NodeList.nodeList( + new Modifier(Modifier.Keyword.PRIVATE), + new Modifier(Modifier.Keyword.STATIC)), + false, + "NativeCodeHolder"); + type.addMember(holderClass); + + holderClass.addField( + parseType("byte[][]"), "CODE", Modifier.Keyword.STATIC, Modifier.Keyword.FINAL); + + var initBody = holderClass.addStaticInitializer(); + + initBody.addStatement( + StaticJavaParser.parseStatement( + "var host = RedlineTarget.detectHost().orElse(null);")); + + initBody.addStatement( + StaticJavaParser.parseStatement( + "if (host == null) {\n" + + " CODE = null;\n" + + "} else {\n" + + " String resource = \"" + + moduleName + + ".\" + host.resourceSuffix() + \".native\";\n" + + " try (InputStream in = " + + moduleName + + ".class.getResourceAsStream(resource)) {\n" + + " CODE = (in == null) ? null" + + " : NativeCodeSerializer.deserialize(in);\n" + + " } catch (IOException e) {\n" + + " throw new UncheckedIOException(" + + "\"Failed to load native code\", e);\n" + + " }\n" + + "}")); + } + + private static void generateLoadNativeCodeMethod(ClassOrInterfaceDeclaration type) { + var method = + type.addMethod("loadNativeCode", Modifier.Keyword.PUBLIC, Modifier.Keyword.STATIC) + .setType(parseType("byte[][]")); + method.createBody() + .addStatement( + new ReturnStmt( + new FieldAccessExpr(new NameExpr("NativeCodeHolder"), "CODE"))); + } + + private static void generateBuilderMethod(ClassOrInterfaceDeclaration type, String moduleName) { + var method = + type.addMethod("builder", Modifier.Keyword.PUBLIC, Modifier.Keyword.STATIC) + .setType(parseClassOrInterfaceType("Instance.Builder")); + + var body = method.createBody(); + body.addStatement(StaticJavaParser.parseStatement("var module = load();")); + body.addStatement( + StaticJavaParser.parseStatement("byte[][] nativeCode = loadNativeCode();")); + body.addStatement( + StaticJavaParser.parseStatement( + "if (nativeCode != null) {\n" + + " var provider = NativeMachineFactoryProvider.discover();\n" + + " if (provider.isPresent()) {\n" + + " return provider.get().builder(module, nativeCode);\n" + + " }\n" + + "}")); + body.addStatement( + StaticJavaParser.parseStatement( + "return Instance.builder(module).withMachineFactory(" + + moduleName + + "::create);")); + } + + private static void generateSafeBuilderMethod( + ClassOrInterfaceDeclaration type, String moduleName) { + var method = + type.addMethod("safeBuilder", Modifier.Keyword.PUBLIC, Modifier.Keyword.STATIC) + .setType(parseClassOrInterfaceType("Instance.Builder")); + + method.createBody() + .addStatement( + StaticJavaParser.parseStatement( + "return Instance.builder(load()).withMachineFactory(" + + moduleName + + "::create);")); + } +} diff --git a/redline/it/pom.xml b/redline/it/pom.xml new file mode 100644 index 000000000..91c36dc9d --- /dev/null +++ b/redline/it/pom.xml @@ -0,0 +1,68 @@ + + + 4.0.0 + + + run.endive + redline-parent-experimental + 999-SNAPSHOT + ../pom.xml + + redline-it-experimental + jar + Endive - Redline IT + Integration tests for the Redline native compiler + + + true + + + + + run.endive + endive-compiler-maven-plugin + + + run.endive + redline-runner-jffi-experimental + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + run.endive:endive-compiler-maven-plugin + run.endive:redline-runner-jffi-experimental + + + + + org.apache.maven.plugins + maven-invoker-plugin + + ${project.build.directory}/it + true + src/it/settings.xml + verify + true + ${skipTests} + true + invoker.properties + + + + integration-tests + + install + run + + + + + + + diff --git a/redline/it/src/it/redline-e2e/invoker.properties b/redline/it/src/it/redline-e2e/invoker.properties new file mode 100644 index 000000000..84099bc5a --- /dev/null +++ b/redline/it/src/it/redline-e2e/invoker.properties @@ -0,0 +1 @@ +invoker.goals=test diff --git a/redline/it/src/it/redline-e2e/pom.xml b/redline/it/src/it/redline-e2e/pom.xml new file mode 100644 index 000000000..123640cb7 --- /dev/null +++ b/redline/it/src/it/redline-e2e/pom.xml @@ -0,0 +1,80 @@ + + + + 4.0.0 + run.endive + redline-e2e-it + 0.0-SNAPSHOT + jar + + + + run.endive + redline-api-experimental + @project.version@ + + + run.endive + redline-runner-jffi-experimental + @project.version@ + + + run.endive + runtime + @project.version@ + + + + org.junit.jupiter + junit-jupiter-api + @junit.version@ + test + + + org.junit.jupiter + junit-jupiter-engine + @junit.version@ + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + @maven-compiler-plugin.version@ + + 11 + + + + run.endive + endive-compiler-maven-plugin + @project.version@ + + + run.endive + redline-build-time-compiler-experimental + @project.version@ + + + + + compile-i32 + + compile + + + endive.test.AddModule + src/test/resources/add.wat.wasm + WARN + true + + + + + + + + diff --git a/redline/it/src/it/redline-e2e/src/test/java/endive/test/RedlineE2eTest.java b/redline/it/src/it/redline-e2e/src/test/java/endive/test/RedlineE2eTest.java new file mode 100644 index 000000000..0233cafb1 --- /dev/null +++ b/redline/it/src/it/redline-e2e/src/test/java/endive/test/RedlineE2eTest.java @@ -0,0 +1,69 @@ +package endive.test; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; +import run.endive.redline.experimental.api.NativeMachineFactoryProvider; + +class RedlineE2eTest { + + @Test + public void nativeBuilderProducesCorrectResults() { + try (var instance = AddModule.builder().build()) { + var add = instance.export("add"); + assertArrayEquals(new long[] {3}, add.apply(1, 2)); + assertArrayEquals(new long[] {0}, add.apply(0, 0)); + assertEquals( + (int) add.apply(0, -1)[0], + -1, + "i32 add(0, -1) should be -1 when narrowed to int"); + } + } + + @Test + public void safeBuilderProducesCorrectResults() { + try (var instance = AddModule.safeBuilder().build()) { + var add = instance.export("add"); + assertArrayEquals(new long[] {3}, add.apply(1, 2)); + assertArrayEquals(new long[] {0}, add.apply(0, 0)); + assertEquals( + (int) add.apply(0, -1)[0], + -1, + "i32 add(0, -1) should be -1 when narrowed to int"); + } + } + + @Test + public void nativeCodeIsAvailable() { + assertNotNull( + AddModule.loadNativeCode(), "Native code should be available on this platform"); + } + + @Test + public void spiDiscoversProvider() { + var provider = NativeMachineFactoryProvider.discover(); + assertTrue(provider.isPresent(), "Should discover a native provider"); + assertTrue(provider.get().priority() > 0, "Provider should have positive priority"); + } + + @Test + public void bothBuildersProduceSameResults() { + try (var nativeInstance = AddModule.builder().build(); + var safeInstance = AddModule.safeBuilder().build()) { + var nativeAdd = nativeInstance.export("add"); + var safeAdd = safeInstance.export("add"); + + for (int a = -10; a <= 10; a++) { + for (int b = -10; b <= 10; b++) { + assertEquals( + (int) safeAdd.apply(a, b)[0], + (int) nativeAdd.apply(a, b)[0], + "add(" + a + ", " + b + ") should match"); + } + } + } + } +} diff --git a/redline/it/src/it/redline-e2e/src/test/resources/add.wat.wasm b/redline/it/src/it/redline-e2e/src/test/resources/add.wat.wasm new file mode 100644 index 000000000..ad1f2f7a3 Binary files /dev/null and b/redline/it/src/it/redline-e2e/src/test/resources/add.wat.wasm differ diff --git a/redline/it/src/it/settings.xml b/redline/it/src/it/settings.xml new file mode 100644 index 000000000..2d90068bb --- /dev/null +++ b/redline/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/redline/pom.xml b/redline/pom.xml index 13f29061f..2c617995f 100644 --- a/redline/pom.xml +++ b/redline/pom.xml @@ -17,7 +17,9 @@ api bridge + build-time-compiler compiler + it runner-jffi runner-jffi-tests