From 942d416898b3db4e9a9a904e73655d2a446fea8a Mon Sep 17 00:00:00 2001 From: andreatp Date: Fri, 24 Jul 2026 12:47:22 +0100 Subject: [PATCH 1/6] Extend Maven plugin with redline native compilation support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Architecture: - Bridge module uses exec:java with Generator.main() instead of the Maven plugin, breaking the circular dependency (plugin → redline → bridge → plugin) - New redline/build-time-compiler module with RedlineGenerator that handles native code generation and extends generated sources with builder()/safeBuilder()/loadNativeCode() methods - Maven plugin calls RedlineGenerator when is configured Config extended with: - redlineTargets: list of target triples for cross-compilation - targetResourceFolder: where .native files are written Generated module class gains (when redlineTargets configured): - loadNativeCode(): loads platform-specific native code from resources - builder(): automatic backend selection (native if platform + Java 25 supported, bytecode otherwise) - safeBuilder(): always uses bytecode compiler NativeMachineFactory.Builder gains toInstanceBuilder() to return a configured Instance.Builder for the generated builder() method. RedlineTarget gains fromTriple() for target triple lookup. --- bom/pom.xml | 5 + .../endive/build/time/compiler/Config.java | 45 ++++- .../endive/build/time/compiler/Generator.java | 31 ++++ compiler-maven-plugin/pom.xml | 4 + .../time/maven/EndiveCompilerGenMojo.java | 33 +++- pom.xml | 5 + .../api/internal/RedlineTarget.java | 9 + redline/bridge/pom.xml | 71 +++++++- redline/build-time-compiler/pom.xml | 38 ++++ .../experimental/build/RedlineGenerator.java | 172 ++++++++++++++++++ redline/pom.xml | 2 + 11 files changed, 403 insertions(+), 12 deletions(-) create mode 100644 redline/build-time-compiler/pom.xml create mode 100644 redline/build-time-compiler/src/main/java/run/endive/redline/experimental/build/RedlineGenerator.java 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/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/Generator.java b/build-time-compiler/src/main/java/run/endive/build/time/compiler/Generator.java index 1aacbf2cd..8e4b73092 100644 --- a/build-time-compiler/src/main/java/run/endive/build/time/compiler/Generator.java +++ b/build-time-compiler/src/main/java/run/endive/build/time/compiler/Generator.java @@ -56,6 +56,37 @@ public Generator(Config config) { this.config = config; } + public static void main(String[] args) throws IOException { + if (args.length < 5) { + throw new IllegalArgumentException( + "Usage: Generator " + + " " + + " [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( + run.endive.compiler.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()); + } + } + public Set generateResources() throws IOException { var module = Parser.parse(config.wasmFile()); var machineName = config.name() + "Machine"; diff --git a/compiler-maven-plugin/pom.xml b/compiler-maven-plugin/pom.xml index 223b8df00..a7ea02a6a 100644 --- a/compiler-maven-plugin/pom.xml +++ b/compiler-maven-plugin/pom.xml @@ -22,6 +22,10 @@ run.endive compiler + + run.endive + redline-build-time-compiler-experimental + org.apache.maven maven-core 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..ee1c3a82b 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; @@ -14,6 +15,7 @@ import run.endive.build.time.compiler.Config; import run.endive.build.time.compiler.Generator; import run.endive.compiler.InterpreterFallback; +import run.endive.redline.experimental.build.RedlineGenerator; /** * This plugin generates an invokable library from the compiled Wasm @@ -77,6 +79,23 @@ public class EndiveCompilerGenMojo extends AbstractMojo { @Parameter(required = false) String moduleInterface; + /** + * Target triples for Redline native compilation. When set, the plugin + * cross-compiles the Wasm module to native code for each target using + * Cranelift and generates builder()/safeBuilder() methods in the module class. + * Example: x86_64-unknown-linux-gnu, aarch64-apple-darwin + */ + @Parameter(required = false) + List redlineTargets; + + /** + * The target resource folder for native code files (.native). + */ + @Parameter( + required = true, + defaultValue = "${project.build.directory}/generated-resources/endive-compiler") + private File targetResourceFolder; + /** * The current Maven project. */ @@ -87,7 +106,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 +116,11 @@ public void execute() throws MojoExecutionException { .withInterpreterFallback(interpreterFallback) .withInterpretedFunctions(interpretedFunctions) .withModuleInterface(moduleInterface) - .build(); + .withTargetResourceFolder(targetResourceFolder.toPath()); + if (redlineTargets != null && !redlineTargets.isEmpty()) { + configBuilder.withRedlineTargets(redlineTargets); + } + var config = configBuilder.build(); var generator = new Generator(config); @@ -106,6 +129,12 @@ public void execute() throws MojoExecutionException { generator.generateMetaWasm(finalInterpretedFunctions); generator.generateSources(); + if (config.hasRedlineTargets()) { + var redlineGenerator = new RedlineGenerator(config); + redlineGenerator.generateNativeCode(); + redlineGenerator.extendGeneratedSources(); + } + if (moduleInterface != null && !moduleInterface.isEmpty()) { generator.generateModuleInterface(moduleInterface); } diff --git a/pom.xml b/pom.xml index d526ae500..d5999c408 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..784cfdd8a 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.Generator + + ${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..f16ed9840 --- /dev/null +++ b/redline/build-time-compiler/src/main/java/run/endive/redline/experimental/build/RedlineGenerator.java @@ -0,0 +1,172 @@ +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 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 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.internal.RedlineTarget"); + cu.addImport("run.endive.redline.experimental.runner.NativeMachineFactory"); + 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 || Runtime.version().feature() < 25) {\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" + + " return NativeMachineFactory.builder(module)" + + ".withPrecompiledCode(nativeCode)" + + ".toInstanceBuilder();\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/pom.xml b/redline/pom.xml index 13f29061f..e8c0f3c7c 100644 --- a/redline/pom.xml +++ b/redline/pom.xml @@ -17,6 +17,7 @@ api bridge + build-time-compiler compiler runner-jffi runner-jffi-tests @@ -29,6 +30,7 @@ [25,) + it runner runner-tests From 38d1566f3b9c70ef51548abd13a22b2a8d981a82 Mon Sep 17 00:00:00 2001 From: andreatp Date: Fri, 24 Jul 2026 15:04:01 +0100 Subject: [PATCH 2/6] Add true flag and E2E integration test - Add true shortcut to compile for all supported platforms (instead of listing 6 target triples) - Add redline/it module with Maven Invoker integration test: - Compiles add.wat.wasm with native code for all platforms - Tests builder() (native), safeBuilder() (bytecode), loadNativeCode() - Verifies native and bytecode produce identical results - Add logging when redline native compilation runs --- .../endive/experimental/compiler/cli/Cli.java | 19 ++++- .../endive/build/time/compiler/Generator.java | 31 ------- .../build/time/compiler/GeneratorMain.java | 40 +++++++++ .../time/maven/EndiveCompilerGenMojo.java | 48 +++-------- pom.xml | 8 +- redline/bridge/pom.xml | 2 +- .../experimental/build/RedlineGenerator.java | 9 ++ redline/it/pom.xml | 68 ++++++++++++++++ .../it/src/it/redline-e2e/invoker.properties | 1 + redline/it/src/it/redline-e2e/pom.xml | 77 ++++++++++++++++++ .../test/java/endive/test/RedlineE2eTest.java | 53 ++++++++++++ .../src/test/resources/add.wat.wasm | Bin 0 -> 67 bytes redline/it/src/it/settings.xml | 35 ++++++++ 13 files changed, 314 insertions(+), 77 deletions(-) create mode 100644 build-time-compiler/src/main/java/run/endive/build/time/compiler/GeneratorMain.java create mode 100644 redline/it/pom.xml create mode 100644 redline/it/src/it/redline-e2e/invoker.properties create mode 100644 redline/it/src/it/redline-e2e/pom.xml create mode 100644 redline/it/src/it/redline-e2e/src/test/java/endive/test/RedlineE2eTest.java create mode 100644 redline/it/src/it/redline-e2e/src/test/resources/add.wat.wasm create mode 100644 redline/it/src/it/settings.xml 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/Generator.java b/build-time-compiler/src/main/java/run/endive/build/time/compiler/Generator.java index 8e4b73092..1aacbf2cd 100644 --- a/build-time-compiler/src/main/java/run/endive/build/time/compiler/Generator.java +++ b/build-time-compiler/src/main/java/run/endive/build/time/compiler/Generator.java @@ -56,37 +56,6 @@ public Generator(Config config) { this.config = config; } - public static void main(String[] args) throws IOException { - if (args.length < 5) { - throw new IllegalArgumentException( - "Usage: Generator " - + " " - + " [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( - run.endive.compiler.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()); - } - } - public Set generateResources() throws IOException { var module = Parser.parse(config.wasmFile()); var machineName = config.name() + "Machine"; 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/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 ee1c3a82b..d1e8ad898 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 @@ -23,82 +23,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; /** - * Target triples for Redline native compilation. When set, the plugin - * cross-compiles the Wasm module to native code for each target using - * Cranelift and generates builder()/safeBuilder() methods in the module class. - * Example: x86_64-unknown-linux-gnu, aarch64-apple-darwin + * Enable Redline native compilation (experimental) for all supported + * platforms (x86_64 and aarch64 on Linux, macOS, and Windows). */ - @Parameter(required = false) - List redlineTargets; + @Parameter(required = false, defaultValue = "false") + boolean redlineExperimental; /** - * The target resource folder for native code files (.native). + * 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; - /** - * The current Maven project. - */ @Parameter(property = "project", required = true, readonly = true) private MavenProject project; @@ -119,6 +92,8 @@ public void execute() throws MojoExecutionException { .withTargetResourceFolder(targetResourceFolder.toPath()); if (redlineTargets != null && !redlineTargets.isEmpty()) { configBuilder.withRedlineTargets(redlineTargets); + } else if (redlineExperimental) { + configBuilder.withRedlineTargets(RedlineGenerator.allTargets()); } var config = configBuilder.build(); @@ -130,6 +105,7 @@ public void execute() throws MojoExecutionException { generator.generateSources(); if (config.hasRedlineTargets()) { + getLog().info("Redline native compilation for targets: " + config.redlineTargets()); var redlineGenerator = new RedlineGenerator(config); redlineGenerator.generateNativeCode(); redlineGenerator.extendGeneratedSources(); diff --git a/pom.xml b/pom.xml index d5999c408..d6ac3bde2 100644 --- a/pom.xml +++ b/pom.xml @@ -1046,13 +1046,6 @@ - - redline - - redline - - - default-all-modules @@ -1078,6 +1071,7 @@ jmh log machine-tests + redline runtime runtime-tests test-gen-lib diff --git a/redline/bridge/pom.xml b/redline/bridge/pom.xml index 784cfdd8a..bbf6c3627 100644 --- a/redline/bridge/pom.xml +++ b/redline/bridge/pom.xml @@ -87,7 +87,7 @@ generate-sources - run.endive.build.time.compiler.Generator + run.endive.build.time.compiler.GeneratorMain ${project.basedir}/../cranelift_bridge.wasm run.endive.redline.experimental.bridge.internal.Cranelift 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 index f16ed9840..5c5e63f9e 100644 --- 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 @@ -13,6 +13,9 @@ 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; @@ -23,6 +26,12 @@ 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; } diff --git a/redline/it/pom.xml b/redline/it/pom.xml new file mode 100644 index 000000000..0a98d80b6 --- /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-experimental + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + run.endive:endive-compiler-maven-plugin + run.endive:redline-runner-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..34f210861 --- /dev/null +++ b/redline/it/src/it/redline-e2e/pom.xml @@ -0,0 +1,77 @@ + + + + 4.0.0 + run.endive + redline-e2e-it + 0.0-SNAPSHOT + jar + + + 25 + + + + + run.endive + redline-api-experimental + @project.version@ + + + run.endive + redline-runner-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@ + + ${maven.compiler.release} + + + + run.endive + endive-compiler-maven-plugin + @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..d1c06503b --- /dev/null +++ b/redline/it/src/it/redline-e2e/src/test/java/endive/test/RedlineE2eTest.java @@ -0,0 +1,53 @@ +package endive.test; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +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)); + assertArrayEquals(new long[] {-1}, add.apply(0, -1)); + } + } + + @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)); + assertArrayEquals(new long[] {-1}, add.apply(0, -1)); + } + } + + @Test + public void nativeCodeIsAvailable() { + assertNotNull( + AddModule.loadNativeCode(), "Native code should be available on this platform"); + } + + @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++) { + assertArrayEquals( + safeAdd.apply(a, b), + nativeAdd.apply(a, b), + "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 0000000000000000000000000000000000000000..ad1f2f7a3e5ef7d1729d54d08e504be1ef891ae5 GIT binary patch literal 67 zcmWm3u?>JQ3`N2J9E25EfHI?Dlr%`8VF0g=xb_t9L=w(vnPf5KPKn;7t>S8G+`EqD Oi8)x&e^!x+fa?Q1WC;TR literal 0 HcmV?d00001 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 + + + + + + From f58545fb15d50efce9c6549bf214548bb2c9aedd Mon Sep 17 00:00:00 2001 From: andreatp Date: Mon, 10 Aug 2026 11:51:05 +0200 Subject: [PATCH 3/6] Remove -Predline profile, redline always builds - Add Rust toolchain + cranelift_bridge.wasm build to ci.yaml - Remove -Predline from release.yaml and redline.yaml - Plugin depends directly on redline-build-time-compiler (no reflection) - Bridge uses exec:java with GeneratorMain to break the cycle - Generated code uses NativeMachineFactoryProvider SPI (no Panama import) - Remove redundant targetResourceFolder from Config - Validate target triples before compilation in RedlineGenerator - E2E tests: JFFI (all JDKs) + Panama (JDK 25+ only) - Restore javadoc comments on Mojo fields --- .github/workflows/ci.yaml | 7 ++ .github/workflows/redline.yaml | 2 +- .github/workflows/release.yaml | 12 +-- .../endive/build/time/compiler/Config.java | 22 +----- .../time/maven/EndiveCompilerGenMojo.java | 37 +++++++-- redline/bridge/pom.xml | 7 +- .../experimental/build/RedlineGenerator.java | 16 ++-- redline/it/pom.xml | 5 ++ .../it/redline-e2e-panama/invoker.properties | 2 + redline/it/src/it/redline-e2e-panama/pom.xml | 73 ++++++++++++++++++ .../endive/test/RedlinePanamaE2eTest.java | 56 ++++++++++++++ .../src/test/resources/add.wat.wasm | Bin 0 -> 67 bytes redline/it/src/it/redline-e2e/pom.xml | 8 +- .../test/java/endive/test/RedlineE2eTest.java | 26 +++++-- redline/pom.xml | 2 +- 15 files changed, 215 insertions(+), 60 deletions(-) create mode 100644 redline/it/src/it/redline-e2e-panama/invoker.properties create mode 100644 redline/it/src/it/redline-e2e-panama/pom.xml create mode 100644 redline/it/src/it/redline-e2e-panama/src/test/java/endive/test/RedlinePanamaE2eTest.java create mode 100644 redline/it/src/it/redline-e2e-panama/src/test/resources/add.wat.wasm diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ce1816efa..88e43254c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -47,6 +47,13 @@ jobs: distribution: 'temurin' java-version: '${{ matrix.version }}' cache: maven + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + targets: wasm32-wasip1 + - name: Build cranelift_bridge.wasm + working-directory: redline/wasm-build + run: make all - name: Test Java run: ./mvnw -B clean install env: diff --git a/.github/workflows/redline.yaml b/.github/workflows/redline.yaml index fda288f84..088b1f864 100644 --- a/.github/workflows/redline.yaml +++ b/.github/workflows/redline.yaml @@ -36,6 +36,6 @@ jobs: working-directory: redline/wasm-build run: make all - name: Build and test redline - run: ./mvnw -B clean install -Predline + run: ./mvnw -B clean install env: MAVEN_OPTS: "-ea" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 84c647e4f..1dcdfd2b6 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -53,8 +53,7 @@ jobs: run: make all - name: Compile - run: ./mvnw --batch-mode -Dquickly -Predline - + run: ./mvnw --batch-mode -Dquickly - name: Setup Git run: | git config user.name "Endive BOT" @@ -62,8 +61,7 @@ jobs: - name: Set the version run: | - ./mvnw versions:set -DgenerateBackupPoms=false -DnewVersion=${{ github.event.inputs.release-version }} -Predline - git add . + ./mvnw versions:set -DgenerateBackupPoms=false -DnewVersion=${{ github.event.inputs.release-version }} git add . git commit -m "Release version update ${{ github.event.inputs.release-version }}" git push git tag ${{ github.event.inputs.release-version }} @@ -74,8 +72,7 @@ jobs: - name: Release to Maven Central run: | # -Dquickly is needed to locally publish wasm-corpus - ./mvnw --batch-mode -Dquickly -Predline - ./mvnw --batch-mode clean deploy -Drelease -Predline -DskipTests=true -X + ./mvnw --batch-mode -Dquickly ./mvnw --batch-mode clean deploy -Drelease -Predline -DskipTests=true -X env: MAVEN_USERNAME: ${{ secrets.SONATYPE_USERNAME }} MAVEN_CENTRAL_TOKEN: ${{ secrets.SONATYPE_PASSWORD }} @@ -83,8 +80,7 @@ jobs: - name: Back to Snapshot run: | - ./mvnw versions:set -DgenerateBackupPoms=false -DnewVersion=999-SNAPSHOT -Predline - git add . + ./mvnw versions:set -DgenerateBackupPoms=false -DnewVersion=999-SNAPSHOT git add . git commit -m "Snapshot version update" git push env: 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 22a870d6e..5d6a7c484 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 @@ -52,11 +52,6 @@ public final class Config { */ private final List redlineTargets; - /** - * the target resource folder for native code files - */ - private final Path targetResourceFolder; - private Config( Path wasmFile, String name, @@ -66,8 +61,7 @@ private Config( InterpreterFallback interpreterFallback, Set interpretedFunctions, String moduleInterface, - List redlineTargets, - Path targetResourceFolder) { + List redlineTargets) { this.wasmFile = wasmFile; this.name = name; this.targetClassFolder = targetClassFolder; @@ -77,7 +71,6 @@ private Config( this.interpretedFunctions = interpretedFunctions; this.moduleInterface = moduleInterface; this.redlineTargets = redlineTargets; - this.targetResourceFolder = targetResourceFolder; } public Path wasmFile() { @@ -116,10 +109,6 @@ public List redlineTargets() { return redlineTargets; } - public Path targetResourceFolder() { - return targetResourceFolder; - } - public boolean hasRedlineTargets() { return redlineTargets != null && !redlineTargets.isEmpty(); } @@ -154,7 +143,6 @@ public static final class Builder { private Set interpretedFunctions; private String moduleInterface; private List redlineTargets = List.of(); - private Path targetResourceFolder; private Builder() {} @@ -203,11 +191,6 @@ public Builder withRedlineTargets(List redlineTargets) { return this; } - public Builder withTargetResourceFolder(Path targetResourceFolder) { - this.targetResourceFolder = targetResourceFolder; - return this; - } - public Config build() { return new Config( wasmFile, @@ -218,8 +201,7 @@ public Config build() { interpreterFallback, interpretedFunctions, moduleInterface, - redlineTargets, - targetResourceFolder); + redlineTargets); } } } 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 d1e8ad898..371c37367 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 @@ -23,33 +23,59 @@ @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; @@ -67,11 +93,9 @@ public class EndiveCompilerGenMojo extends AbstractMojo { @Parameter(required = false) List redlineTargets; - @Parameter( - required = true, - defaultValue = "${project.build.directory}/generated-resources/endive-compiler") - private File targetResourceFolder; - + /** + * The current Maven project. + */ @Parameter(property = "project", required = true, readonly = true) private MavenProject project; @@ -88,8 +112,7 @@ public void execute() throws MojoExecutionException { .withTargetWasmFolder(targetWasmFolder.toPath()) .withInterpreterFallback(interpreterFallback) .withInterpretedFunctions(interpretedFunctions) - .withModuleInterface(moduleInterface) - .withTargetResourceFolder(targetResourceFolder.toPath()); + .withModuleInterface(moduleInterface); if (redlineTargets != null && !redlineTargets.isEmpty()) { configBuilder.withRedlineTargets(redlineTargets); } else if (redlineExperimental) { diff --git a/redline/bridge/pom.xml b/redline/bridge/pom.xml index bbf6c3627..dd1487f37 100644 --- a/redline/bridge/pom.xml +++ b/redline/bridge/pom.xml @@ -30,10 +30,9 @@ 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 index 5c5e63f9e..6126293a8 100644 --- 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 @@ -43,18 +43,17 @@ public void generateNativeCode() throws IOException { var module = Parser.parse(config.wasmFile()); var packagePath = config.getPackageName().replace('.', '/'); var baseName = config.getBaseName(); - var resourceDir = config.targetResourceFolder().resolve(packagePath); + var resourceDir = config.targetClassFolder().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)); + byte[][] compiledCode = NativeCompiler.compileAll(triple, module); var nativeFile = resourceDir.resolve(baseName + "." + target.resourceSuffix() + ".native"); @@ -77,8 +76,8 @@ public void extendGeneratedSources() throws IOException { 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("run.endive.redline.experimental.runner.NativeMachineFactory"); cu.addImport("java.io.InputStream"); cu.addImport("java.io.IOException"); cu.addImport("java.io.UncheckedIOException"); @@ -114,7 +113,7 @@ private static void generateNativeCodeHolderInnerClass( initBody.addStatement( StaticJavaParser.parseStatement( - "if (host == null || Runtime.version().feature() < 25) {\n" + "if (host == null) {\n" + " CODE = null;\n" + "} else {\n" + " String resource = \"" @@ -154,9 +153,10 @@ private static void generateBuilderMethod(ClassOrInterfaceDeclaration type, Stri body.addStatement( StaticJavaParser.parseStatement( "if (nativeCode != null) {\n" - + " return NativeMachineFactory.builder(module)" - + ".withPrecompiledCode(nativeCode)" - + ".toInstanceBuilder();\n" + + " var provider = NativeMachineFactoryProvider.discover();\n" + + " if (provider.isPresent()) {\n" + + " return provider.get().builder(module, nativeCode);\n" + + " }\n" + "}")); body.addStatement( StaticJavaParser.parseStatement( diff --git a/redline/it/pom.xml b/redline/it/pom.xml index 0a98d80b6..56b4a20da 100644 --- a/redline/it/pom.xml +++ b/redline/it/pom.xml @@ -26,6 +26,10 @@ run.endive redline-runner-experimental + + run.endive + redline-runner-jffi-experimental + @@ -36,6 +40,7 @@ run.endive:endive-compiler-maven-plugin + run.endive:redline-runner-jffi-experimental run.endive:redline-runner-experimental diff --git a/redline/it/src/it/redline-e2e-panama/invoker.properties b/redline/it/src/it/redline-e2e-panama/invoker.properties new file mode 100644 index 000000000..d89167bdd --- /dev/null +++ b/redline/it/src/it/redline-e2e-panama/invoker.properties @@ -0,0 +1,2 @@ +invoker.goals=test +invoker.java.version=25+ diff --git a/redline/it/src/it/redline-e2e-panama/pom.xml b/redline/it/src/it/redline-e2e-panama/pom.xml new file mode 100644 index 000000000..cab4de5c8 --- /dev/null +++ b/redline/it/src/it/redline-e2e-panama/pom.xml @@ -0,0 +1,73 @@ + + + + 4.0.0 + run.endive + redline-e2e-panama-it + 0.0-SNAPSHOT + jar + + + + run.endive + redline-api-experimental + @project.version@ + + + run.endive + redline-runner-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@ + + 25 + + + + run.endive + endive-compiler-maven-plugin + @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-panama/src/test/java/endive/test/RedlinePanamaE2eTest.java b/redline/it/src/it/redline-e2e-panama/src/test/java/endive/test/RedlinePanamaE2eTest.java new file mode 100644 index 000000000..7f83f2df7 --- /dev/null +++ b/redline/it/src/it/redline-e2e-panama/src/test/java/endive/test/RedlinePanamaE2eTest.java @@ -0,0 +1,56 @@ +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 RedlinePanamaE2eTest { + + @Test + public void panamaProviderIsSelected() { + var provider = NativeMachineFactoryProvider.discover(); + assertTrue(provider.isPresent(), "Should discover a native provider"); + assertEquals(100, provider.get().priority(), "Panama should win with priority 100"); + } + + @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 nativeCodeIsAvailable() { + assertNotNull( + AddModule.loadNativeCode(), "Native code should be available on this platform"); + } + + @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-panama/src/test/resources/add.wat.wasm b/redline/it/src/it/redline-e2e-panama/src/test/resources/add.wat.wasm new file mode 100644 index 0000000000000000000000000000000000000000..ad1f2f7a3e5ef7d1729d54d08e504be1ef891ae5 GIT binary patch literal 67 zcmWm3u?>JQ3`N2J9E25EfHI?Dlr%`8VF0g=xb_t9L=w(vnPf5KPKn;7t>S8G+`EqD Oi8)x&e^!x+fa?Q1WC;TR literal 0 HcmV?d00001 diff --git a/redline/it/src/it/redline-e2e/pom.xml b/redline/it/src/it/redline-e2e/pom.xml index 34f210861..09c52c649 100644 --- a/redline/it/src/it/redline-e2e/pom.xml +++ b/redline/it/src/it/redline-e2e/pom.xml @@ -7,10 +7,6 @@ 0.0-SNAPSHOT jar - - 25 - - run.endive @@ -19,7 +15,7 @@ run.endive - redline-runner-experimental + redline-runner-jffi-experimental @project.version@ @@ -49,7 +45,7 @@ maven-compiler-plugin @maven-compiler-plugin.version@ - ${maven.compiler.release} + 11 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 index d1c06503b..baf2dd4f9 100644 --- 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 @@ -1,19 +1,32 @@ 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 jffiProviderIsSelected() { + var provider = NativeMachineFactoryProvider.discover(); + assertTrue(provider.isPresent(), "Should discover a native provider"); + assertEquals(50, provider.get().priority(), "JFFI should be selected with priority 50"); + } + @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)); - assertArrayEquals(new long[] {-1}, add.apply(0, -1)); + assertEquals( + (int) add.apply(0, -1)[0], + -1, + "i32 add(0, -1) should be -1 when narrowed to int"); } } @@ -23,7 +36,10 @@ public void safeBuilderProducesCorrectResults() { var add = instance.export("add"); assertArrayEquals(new long[] {3}, add.apply(1, 2)); assertArrayEquals(new long[] {0}, add.apply(0, 0)); - assertArrayEquals(new long[] {-1}, add.apply(0, -1)); + assertEquals( + (int) add.apply(0, -1)[0], + -1, + "i32 add(0, -1) should be -1 when narrowed to int"); } } @@ -42,9 +58,9 @@ public void bothBuildersProduceSameResults() { for (int a = -10; a <= 10; a++) { for (int b = -10; b <= 10; b++) { - assertArrayEquals( - safeAdd.apply(a, b), - nativeAdd.apply(a, b), + assertEquals( + (int) safeAdd.apply(a, b)[0], + (int) nativeAdd.apply(a, b)[0], "add(" + a + ", " + b + ") should match"); } } diff --git a/redline/pom.xml b/redline/pom.xml index e8c0f3c7c..2c617995f 100644 --- a/redline/pom.xml +++ b/redline/pom.xml @@ -19,6 +19,7 @@ bridge build-time-compiler compiler + it runner-jffi runner-jffi-tests @@ -30,7 +31,6 @@ [25,) - it runner runner-tests From b288dd10d6f87897b14ff8059917906d82379bd9 Mon Sep 17 00:00:00 2001 From: andreatp Date: Wed, 12 Aug 2026 15:57:06 +0200 Subject: [PATCH 4/6] Use inlay to fetch cranelift_bridge.wasm from GHCR Replace Rust toolchain + make all in CI with inlay-maven-plugin fetching the pre-built wasm from ghcr.io/bytecodealliance/endive-cranelift-bridge. No Rust needed in any CI workflow except the new wasm-publish.yaml. - Bridge POM: inlay:fetch before exec:java GeneratorMain - Root POM: add inlay-maven-plugin to pluginManagement - CI/redline/release workflows: remove Rust toolchain steps - New wasm-publish.yaml: build + publish wasm to GHCR on wasm-build changes - wkg.lock: pins wasm digest for reproducible builds --- .github/workflows/ci.yaml | 7 ------ .github/workflows/redline.yaml | 7 ------ .github/workflows/release.yaml | 9 ------- .github/workflows/wasm-publish.yaml | 38 +++++++++++++++++++++++++++++ pom.xml | 6 +++++ redline/bridge/pom.xml | 26 +++++++++++++++++--- redline/wkg.lock | 12 +++++++++ 7 files changed, 79 insertions(+), 26 deletions(-) create mode 100644 .github/workflows/wasm-publish.yaml create mode 100644 redline/wkg.lock diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 88e43254c..ce1816efa 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -47,13 +47,6 @@ jobs: distribution: 'temurin' java-version: '${{ matrix.version }}' cache: maven - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable - with: - targets: wasm32-wasip1 - - name: Build cranelift_bridge.wasm - working-directory: redline/wasm-build - run: make all - name: Test Java run: ./mvnw -B clean install env: diff --git a/.github/workflows/redline.yaml b/.github/workflows/redline.yaml index 088b1f864..961a7861b 100644 --- a/.github/workflows/redline.yaml +++ b/.github/workflows/redline.yaml @@ -28,13 +28,6 @@ jobs: distribution: 'temurin' java-version: '25' cache: maven - - name: Set up Rust - uses: dtolnay/rust-toolchain@stable - with: - targets: wasm32-wasip1 - - name: Build cranelift_bridge.wasm - working-directory: redline/wasm-build - run: make all - name: Build and test redline run: ./mvnw -B clean install env: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 1dcdfd2b6..dccc0f033 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -37,21 +37,12 @@ jobs: gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} gpg-passphrase: MAVEN_GPG_PASSPHRASE - - name: Set up Rust - uses: dtolnay/rust-toolchain@stable - with: - targets: wasm32-wasip1 - - id: install-secret-key name: Install gpg secret key run: | cat <(echo -e "${{ secrets.GPG_PRIVATE_KEY }}") | gpg --batch --import gpg --list-secret-keys --keyid-format LONG - - name: Build cranelift_bridge.wasm - working-directory: redline/wasm-build - run: make all - - name: Compile run: ./mvnw --batch-mode -Dquickly - name: Setup Git diff --git a/.github/workflows/wasm-publish.yaml b/.github/workflows/wasm-publish.yaml new file mode 100644 index 000000000..b95b8e27b --- /dev/null +++ b/.github/workflows/wasm-publish.yaml @@ -0,0 +1,38 @@ +name: Publish cranelift_bridge.wasm + +on: + push: + branches: [main] + paths: ['redline/wasm-build/**'] + workflow_dispatch: + +jobs: + build-and-publish: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + id-token: write + steps: + - name: Checkout sources + uses: actions/checkout@v7 + + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: wasm32-wasip1 + + - name: Build cranelift_bridge.wasm + working-directory: redline/wasm-build + run: make all + + - name: Install ORAS + uses: oras-project/setup-oras@v1 + + - name: Login to GHCR + run: echo "${{ secrets.GITHUB_TOKEN }}" | oras login ghcr.io -u ${{ github.actor }} --password-stdin + + - name: Push to GHCR + run: | + oras push ghcr.io/bytecodealliance/endive-cranelift-bridge:latest \ + redline/cranelift_bridge.wasm:application/wasm diff --git a/pom.xml b/pom.xml index d6ac3bde2..914850b22 100644 --- a/pom.xml +++ b/pom.xml @@ -93,6 +93,7 @@ 3.10.1 0.11.0 3.6.3 + 0.0.2 3.9.0 3.1.0 3.2.0 @@ -414,6 +415,11 @@ spotless-maven-plugin ${spotless-maven-plugin.version} + + io.roastedroot + inlay-maven-plugin + ${inlay-maven-plugin.version} + org.apache.maven.plugins maven-antrun-plugin diff --git a/redline/bridge/pom.xml b/redline/bridge/pom.xml index dd1487f37..eb32af6c7 100644 --- a/redline/bridge/pom.xml +++ b/redline/bridge/pom.xml @@ -30,11 +30,31 @@ + + io.roastedroot + inlay-maven-plugin + + + fetch-cranelift-bridge + + fetch + + + + + ghcr.io/bytecodealliance/endive-cranelift-bridge:999.0.0-SNAPSHOT + ${project.basedir}/../cranelift_bridge.wasm + + + ${project.basedir}/../wkg.lock + + + + org.codehaus.mojo build-helper-maven-plugin diff --git a/redline/wkg.lock b/redline/wkg.lock new file mode 100644 index 000000000..bd266a4ab --- /dev/null +++ b/redline/wkg.lock @@ -0,0 +1,12 @@ +# This file is automatically generated. +# It is not intended for manual editing. +version = 1 + +[[packages]] +name = "bytecodealliance:endive-cranelift-bridge" +registry = "ghcr.io" + +[[packages.versions]] +requirement = "=999.0.0-SNAPSHOT" +version = "999.0.0-SNAPSHOT" +digest = "sha256:2436e889dae98422313f495632557068dda466e8170aa718a3e70d7fee1d652f" From a661d3af02c61673bb63e0cdd699fa1e85c83893 Mon Sep 17 00:00:00 2001 From: andreatp Date: Fri, 14 Aug 2026 11:22:56 +0200 Subject: [PATCH 5/6] Remove Panama runner dep from it module The it module is unconditional (builds on all JDKs) but redline-runner-experimental requires JDK 25+. The Panama E2E test project declares its own dependency via @project.version@. --- redline/it/pom.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/redline/it/pom.xml b/redline/it/pom.xml index 56b4a20da..91c36dc9d 100644 --- a/redline/it/pom.xml +++ b/redline/it/pom.xml @@ -22,10 +22,6 @@ run.endive endive-compiler-maven-plugin - - run.endive - redline-runner-experimental - run.endive redline-runner-jffi-experimental @@ -41,7 +37,6 @@ run.endive:endive-compiler-maven-plugin run.endive:redline-runner-jffi-experimental - run.endive:redline-runner-experimental From ef9ae2c581a9459636ae7e4c214f1490a2479458 Mon Sep 17 00:00:00 2001 From: andreatp Date: Fri, 14 Aug 2026 18:47:40 +0200 Subject: [PATCH 6/6] Gate Panama runner dep in it module behind java25 profile The it module is unconditional but redline-runner-experimental requires JDK 25+. Move it to a java25 profile so it only resolves on JDK 25+ where the Panama E2E test actually runs. --- redline/it/pom.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/redline/it/pom.xml b/redline/it/pom.xml index 91c36dc9d..42e7fb2ee 100644 --- a/redline/it/pom.xml +++ b/redline/it/pom.xml @@ -37,6 +37,7 @@ run.endive:endive-compiler-maven-plugin run.endive:redline-runner-jffi-experimental + run.endive:redline-runner-experimental @@ -65,4 +66,19 @@ + + + + java25 + + [25,) + + + + run.endive + redline-runner-experimental + + + +