From 67a2c40d80af343f7474b06d87df492363250f69 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 10:02:30 +0000 Subject: [PATCH 1/2] Add support for invokedynamic in ByteCodeTranslator for Java 8 lambdas and method references. - Implemented `visitInvokeDynamicInsn` in `Parser.java` to translate lambda expressions and method references into static proxy classes. - Updated `vm/JavaAPI/pom.xml` to use source/target 1.8 to support newer build environments and lambdas. - Added `LambdaIntegrationTest.java` in `vm/tests` to verify end-to-end compilation and execution of Java 8 lambda code translated to C. - Handled generation of necessary stubs (`java.lang.invoke`, `java.util.Objects`) in integration tests. --- .../codename1/tools/translator/Parser.java | 166 ++++++ vm/JavaAPI/pom.xml | 4 +- .../translator/LambdaIntegrationTest.java | 548 ++++++++++++++++++ 3 files changed, 716 insertions(+), 2 deletions(-) create mode 100644 vm/tests/src/test/java/com/codename1/tools/translator/LambdaIntegrationTest.java diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java index 87b37bfbcb..dbc8d9f510 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java @@ -35,6 +35,7 @@ import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; import org.objectweb.asm.TypePath; import org.objectweb.asm.commons.JSRInlinerAdapter; @@ -49,6 +50,7 @@ public class Parser extends ClassVisitor { private String clsName; private static String[] nativeSources; private static List classes = new ArrayList(); + private int lambdaCounter; public static void cleanup() { nativeSources = null; classes.clear(); @@ -836,6 +838,170 @@ public void visitJumpInsn(int opcode, Label label) { @Override public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { + if ("java/lang/invoke/LambdaMetafactory".equals(bsm.getOwner()) && + ("metafactory".equals(bsm.getName()) || "altMetafactory".equals(bsm.getName()))) { + + // 1. Generate a unique class name for the lambda + String lambdaClassName = clsName + "_lambda_" + (lambdaCounter++); + + // 2. Create the ByteCodeClass for the lambda + ByteCodeClass lambdaClass = new ByteCodeClass(lambdaClassName, lambdaClassName.replace('_', '/')); + lambdaClass.setBaseClass("java/lang/Object"); + + // The interface implemented is the return type of the invokedynamic descriptor + Type invokedType = Type.getMethodType(desc); + Type interfaceType = invokedType.getReturnType(); + lambdaClass.setBaseInterfaces(new String[]{interfaceType.getInternalName()}); + + // 3. Add fields for captured arguments + Type[] capturedArgs = invokedType.getArgumentTypes(); + for (int i = 0; i < capturedArgs.length; i++) { + String fieldName = "arg$" + (i + 1); + String fieldDesc = capturedArgs[i].getDescriptor(); + ByteCodeField field = new ByteCodeField(lambdaClassName, Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, fieldName, fieldDesc, null, null); + lambdaClass.addField(field); + } + + // 4. Add Constructor + StringBuilder ctorDesc = new StringBuilder("("); + for (Type t : capturedArgs) { + ctorDesc.append(t.getDescriptor()); + } + ctorDesc.append(")V"); + + BytecodeMethod ctor = new BytecodeMethod(lambdaClassName, Opcodes.ACC_PUBLIC, "", ctorDesc.toString(), null, null); + lambdaClass.addMethod(ctor); + + // Constructor body (we need to generate instructions manually) + // ALOAD 0 + // INVOKESPECIAL java/lang/Object. + // ... assign fields ... + // RETURN + + ctor.addInstruction(Opcodes.ALOAD); // 25 + ctor.addVariableOperation(Opcodes.ALOAD, 0); + ctor.addInvoke(Opcodes.INVOKESPECIAL, "java/lang/Object", "", "()V", false); + + int varIndex = 1; + for (int i = 0; i < capturedArgs.length; i++) { + ctor.addInstruction(Opcodes.ALOAD); + ctor.addVariableOperation(Opcodes.ALOAD, 0); // this + + Type t = capturedArgs[i]; + int opcode = t.getOpcode(Opcodes.ILOAD); // correct load opcode for type + ctor.addVariableOperation(opcode, varIndex); + varIndex += t.getSize(); + + String fieldName = "arg$" + (i + 1); + ctor.addField(lambdaClass, Opcodes.PUTFIELD, lambdaClassName, fieldName, t.getDescriptor()); + } + ctor.addInstruction(Opcodes.RETURN); + ctor.setMaxes(varIndex + 1, varIndex); // Approximate maxes + + + // 5. Implement the interface method + Type samMethodType = (Type) bsmArgs[0]; + Handle implMethod = (Handle) bsmArgs[1]; + Type instantiatedMethodType = (Type) bsmArgs[2]; + + String samMethodName = name; // Name from invokedynamic + String samMethodDesc = samMethodType.getDescriptor(); // Signature from BSM arg 0 + + BytecodeMethod interfaceMethod = new BytecodeMethod(lambdaClassName, Opcodes.ACC_PUBLIC, samMethodName, samMethodDesc, null, null); + lambdaClass.addMethod(interfaceMethod); + + // Method Body: + // Load captured arguments from fields + // Load method arguments + // Invoke implMethod + // Return result + + // Handle Constructor Reference (special case) + boolean isCtorRef = (implMethod.getTag() == Opcodes.H_NEWINVOKESPECIAL); + if (isCtorRef) { + interfaceMethod.addTypeInstruction(Opcodes.NEW, implMethod.getOwner()); + interfaceMethod.addInstruction(Opcodes.DUP); + } + + // Load captured args + for (int i = 0; i < capturedArgs.length; i++) { + interfaceMethod.addInstruction(Opcodes.ALOAD); + interfaceMethod.addVariableOperation(Opcodes.ALOAD, 0); + String fieldName = "arg$" + (i + 1); + interfaceMethod.addField(lambdaClass, Opcodes.GETFIELD, lambdaClassName, fieldName, capturedArgs[i].getDescriptor()); + } + + // Load method args + Type[] samArgs = samMethodType.getArgumentTypes(); + int localIndex = 1; + for (Type t : samArgs) { + interfaceMethod.addVariableOperation(t.getOpcode(Opcodes.ILOAD), localIndex); + localIndex += t.getSize(); + } + + // Invoke implMethod + int invokeOpcode; + switch (implMethod.getTag()) { + case Opcodes.H_INVOKESTATIC: invokeOpcode = Opcodes.INVOKESTATIC; break; + case Opcodes.H_INVOKEVIRTUAL: invokeOpcode = Opcodes.INVOKEVIRTUAL; break; + case Opcodes.H_INVOKEINTERFACE: invokeOpcode = Opcodes.INVOKEINTERFACE; break; + case Opcodes.H_INVOKESPECIAL: invokeOpcode = Opcodes.INVOKESPECIAL; break; + case Opcodes.H_NEWINVOKESPECIAL: invokeOpcode = Opcodes.INVOKESPECIAL; break; + default: invokeOpcode = Opcodes.INVOKESTATIC; // Fallback + } + + if (isCtorRef) { + interfaceMethod.addInvoke(Opcodes.INVOKESPECIAL, implMethod.getOwner(), implMethod.getName(), implMethod.getDesc(), false); + } else { + interfaceMethod.addInvoke(invokeOpcode, implMethod.getOwner(), implMethod.getName(), implMethod.getDesc(), implMethod.isInterface()); + } + + // Return + Type returnType = samMethodType.getReturnType(); + interfaceMethod.addInstruction(returnType.getOpcode(Opcodes.IRETURN)); + interfaceMethod.setMaxes(20, 20); // Approximation + + + // 6. Add static factory method + String factoryMethodName = "lambda$factory"; + String factoryDesc = desc; // The desc of invokedynamic is (CapturedArgs)Interface. + + // We want factory to be (CapturedArgs)LambdaClass (to match NEW output but wrapped) + // Actually, replacing invokedynamic with INVOKESTATIC means the return type on stack should match + // what invokedynamic promised, which is the Interface. + // Our factory returns LambdaClass, which implements Interface. So it's assignment compatible. + // However, the method signature in C needs to return an object pointer anyway. + + // Let's make the factory return the class type explicitly in signature + String factoryRetType = "L" + lambdaClassName + ";"; + String actualFactoryDesc = desc.substring(0, desc.lastIndexOf(')') + 1) + factoryRetType; + + BytecodeMethod factory = new BytecodeMethod(lambdaClassName, Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, factoryMethodName, actualFactoryDesc, null, null); + lambdaClass.addMethod(factory); + + factory.addTypeInstruction(Opcodes.NEW, lambdaClassName); + factory.addInstruction(Opcodes.DUP); + + // Load factory arguments (captured args) + localIndex = 0; // Static method + for (Type t : capturedArgs) { + factory.addVariableOperation(t.getOpcode(Opcodes.ILOAD), localIndex); + localIndex += t.getSize(); + } + + factory.addInvoke(Opcodes.INVOKESPECIAL, lambdaClassName, "", ctorDesc.toString(), false); + factory.addInstruction(Opcodes.ARETURN); + factory.setMaxes(localIndex + 2, localIndex); + + // 7. Register the new class + classes.add(lambdaClass); + + // 8. Replace invokedynamic with INVOKESTATIC to factory + mtd.addInvoke(Opcodes.INVOKESTATIC, lambdaClassName, factoryMethodName, actualFactoryDesc, false); + + return; + } + super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); } diff --git a/vm/JavaAPI/pom.xml b/vm/JavaAPI/pom.xml index a74120c9bb..6110119833 100644 --- a/vm/JavaAPI/pom.xml +++ b/vm/JavaAPI/pom.xml @@ -21,8 +21,8 @@ maven-compiler-plugin 3.11.0 - 1.5 - 1.5 + 1.8 + 1.8 -Xlint:-options diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/LambdaIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/LambdaIntegrationTest.java new file mode 100644 index 0000000000..41e379a0b0 --- /dev/null +++ b/vm/tests/src/test/java/com/codename1/tools/translator/LambdaIntegrationTest.java @@ -0,0 +1,548 @@ +package com.codename1.tools.translator; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import javax.tools.JavaCompiler; +import javax.tools.ToolProvider; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +class LambdaIntegrationTest { + + private String appSource() { + return "public class LambdaApp {\n" + + " interface MyFunctionalInterface {\n" + + " int apply(int x);\n" + + " }\n" + + " \n" + + " public static void main(String[] args) {\n" + + " // Lambda\n" + + " MyFunctionalInterface adder = (x) -> x + 10;\n" + + " int result1 = adder.apply(5); // 15\n" + + " \n" + + " // Method reference static\n" + + " MyFunctionalInterface multiplier = LambdaApp::multiplyByTwo;\n" + + " int result2 = multiplier.apply(5); // 10\n" + + " \n" + + " // Method reference instance\n" + + " LambdaApp app = new LambdaApp();\n" + + " MyFunctionalInterface subtractor = app::subtractFive;\n" + + " int result3 = subtractor.apply(20); // 15\n" + + "\n" + + " // Capturing lambda\n" + + " int captured = 100;\n" + + " MyFunctionalInterface capturer = (x) -> x + captured;\n" + + " int result4 = capturer.apply(5); // 105\n" + + " \n" + + " report(result1 + result2 + result3 + result4); // 15 + 10 + 15 + 105 = 145\n" + + " }\n" + + " \n" + + " static int multiplyByTwo(int x) {\n" + + " return x * 2;\n" + + " }\n" + + " \n" + + " int subtractFive(int x) {\n" + + " return x - 5;\n" + + " }\n" + + "\n" + + " private static native void report(int value);\n" + + "}\n"; + } + + private String nativeReportSource() { + return "#include \"cn1_globals.h\"\n" + + "#include \n" + + "void LambdaApp_report___int(CODENAME_ONE_THREAD_STATE, JAVA_INT value) {\n" + + " printf(\"RESULT=%d\\n\", value);\n" + + "}\n"; + } + + @ParameterizedTest + @ValueSource(strings = {"1.8"}) + void translatesLambdaBytecodeToLLVMExecutable(String targetVersion) throws Exception { + Parser.cleanup(); + + Path sourceDir = Files.createTempDirectory("lambda-integration-sources"); + Path classesDir = Files.createTempDirectory("lambda-integration-classes"); + Path javaApiDir = Files.createTempDirectory("java-api-classes"); + + Files.write(sourceDir.resolve("LambdaApp.java"), appSource().getBytes(StandardCharsets.UTF_8)); + + // Compile JavaAPI for bootclasspath + compileJavaAPI(javaApiDir); + + Path nativeReport = sourceDir.resolve("native_report.c"); + Files.write(nativeReport, nativeReportSource().getBytes(StandardCharsets.UTF_8)); + + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + assertNotNull(compiler, "A JDK is required to compile test sources"); + + // Compile App using JavaAPI as bootclasspath + List compileArgs = new ArrayList<>(); + compileArgs.add("-source"); + compileArgs.add(targetVersion); + compileArgs.add("-target"); + compileArgs.add(targetVersion); + compileArgs.add("-bootclasspath"); + compileArgs.add(javaApiDir.toString()); + compileArgs.add("-d"); + compileArgs.add(classesDir.toString()); + compileArgs.add(sourceDir.resolve("LambdaApp.java").toString()); + + int compileResult = compiler.run( + null, + null, + null, + compileArgs.toArray(new String[0]) + ); + assertEquals(0, compileResult, "LambdaApp should compile"); + + Files.copy(nativeReport, classesDir.resolve("native_report.c")); + + Path outputDir = Files.createTempDirectory("lambda-integration-output"); + CleanTargetIntegrationTest.runTranslator(classesDir, outputDir, "LambdaApp"); + + Path distDir = outputDir.resolve("dist"); + Path cmakeLists = distDir.resolve("CMakeLists.txt"); + assertTrue(Files.exists(cmakeLists), "Translator should emit a CMake project"); + + Path srcRoot = distDir.resolve("LambdaApp-src"); + CleanTargetIntegrationTest.patchCn1Globals(srcRoot); + writeRuntimeStubs(srcRoot); + writeMissingHeadersAndImpls(srcRoot); + + CleanTargetIntegrationTest.replaceLibraryWithExecutableTarget(cmakeLists, srcRoot.getFileName().toString()); + + Path buildDir = distDir.resolve("build"); + Files.createDirectories(buildDir); + + CleanTargetIntegrationTest.runCommand(Arrays.asList( + "cmake", + "-S", distDir.toString(), + "-B", buildDir.toString(), + "-DCMAKE_C_COMPILER=clang", + "-DCMAKE_OBJC_COMPILER=clang" + ), distDir); + + CleanTargetIntegrationTest.runCommand(Arrays.asList("cmake", "--build", buildDir.toString()), distDir); + + Path executable = buildDir.resolve("LambdaApp"); + String output = CleanTargetIntegrationTest.runCommand(Arrays.asList(executable.toString()), buildDir); + assertTrue(output.contains("RESULT=145"), "Compiled program should print the expected result: " + output); + } + + private void compileJavaAPI(Path outputDir) throws Exception { + Files.createDirectories(outputDir); + Path javaApiRoot = Paths.get("..", "JavaAPI", "src").normalize().toAbsolutePath(); + List sources = new ArrayList<>(); + Files.walk(javaApiRoot) + .filter(p -> p.toString().endsWith(".java")) + .forEach(p -> sources.add(p.toString())); + + // Add stubs for java.lang.invoke + Path stubsDir = Files.createTempDirectory("java-lang-invoke-stubs"); + sources.addAll(generateJavaLangInvokeStubs(stubsDir)); + sources.addAll(generateJavaUtilStubs(stubsDir)); + + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + List args = new ArrayList<>(); + + if (!System.getProperty("java.version").startsWith("1.")) { + args.add("--patch-module"); + args.add("java.base=" + javaApiRoot.toString()); + } else { + args.add("-source"); + args.add("1.5"); + args.add("-target"); + args.add("1.5"); + } + + args.add("-d"); + args.add(outputDir.toString()); + args.addAll(sources); + + int result = compiler.run(null, null, null, args.toArray(new String[0])); + assertEquals(0, result, "JavaAPI should compile"); + } + + private List generateJavaUtilStubs(Path stubsDir) throws IOException { + List stubFiles = new ArrayList<>(); + Path utilPkg = stubsDir.resolve("java/util"); + Files.createDirectories(utilPkg); + + // Objects + Path objs = utilPkg.resolve("Objects.java"); + Files.write(objs, ("package java.util;\n" + + "public class Objects {\n" + + " public static T requireNonNull(T obj) { if (obj == null) throw new NullPointerException(); return obj; }\n" + + "}").getBytes(StandardCharsets.UTF_8)); + stubFiles.add(objs.toString()); + + return stubFiles; + } + + private List generateJavaLangInvokeStubs(Path stubsDir) throws IOException { + List stubFiles = new ArrayList<>(); + Path invokePkg = stubsDir.resolve("java/lang/invoke"); + Files.createDirectories(invokePkg); + + // MethodHandle + Path mh = invokePkg.resolve("MethodHandle.java"); + Files.write(mh, ("package java.lang.invoke;\n" + + "public abstract class MethodHandle {\n" + + " public Object invoke(Object... args) throws Throwable { return null; }\n" + + " public Object invokeExact(Object... args) throws Throwable { return null; }\n" + + "}").getBytes(StandardCharsets.UTF_8)); + stubFiles.add(mh.toString()); + + // MethodType + Path mt = invokePkg.resolve("MethodType.java"); + Files.write(mt, ("package java.lang.invoke;\n" + + "public class MethodType {\n" + + " public static MethodType methodType(Class rtype, Class[] ptypes) { return null; }\n" + + "}").getBytes(StandardCharsets.UTF_8)); + stubFiles.add(mt.toString()); + + // MethodHandles + Path mhs = invokePkg.resolve("MethodHandles.java"); + Files.write(mhs, ("package java.lang.invoke;\n" + + "public class MethodHandles {\n" + + " public static Lookup lookup() { return null; }\n" + + " public static class Lookup {\n" + + " public MethodHandle findVirtual(Class refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { return null; }\n" + + " public MethodHandle findStatic(Class refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { return null; }\n" + + " }\n" + + "}").getBytes(StandardCharsets.UTF_8)); + stubFiles.add(mhs.toString()); + + // CallSite + Path cs = invokePkg.resolve("CallSite.java"); + Files.write(cs, ("package java.lang.invoke;\n" + + "public abstract class CallSite {\n" + + " public abstract MethodHandle getTarget();\n" + + " public abstract void setTarget(MethodHandle newTarget);\n" + + "}").getBytes(StandardCharsets.UTF_8)); + stubFiles.add(cs.toString()); + + // LambdaMetafactory + Path lmf = invokePkg.resolve("LambdaMetafactory.java"); + Files.write(lmf, ("package java.lang.invoke;\n" + + "public class LambdaMetafactory {\n" + + " public static CallSite metafactory(MethodHandles.Lookup caller, String invokedName, MethodType invokedType, MethodType samMethodType, MethodHandle implMethod, MethodType instantiatedMethodType) throws LambdaConversionException { return null; }\n" + + " public static CallSite altMetafactory(MethodHandles.Lookup caller, String invokedName, MethodType invokedType, Object... args) throws LambdaConversionException { return null; }\n" + + "}").getBytes(StandardCharsets.UTF_8)); + stubFiles.add(lmf.toString()); + + // LambdaConversionException + Path lce = invokePkg.resolve("LambdaConversionException.java"); + Files.write(lce, ("package java.lang.invoke;\n" + + "public class LambdaConversionException extends Exception {}\n").getBytes(StandardCharsets.UTF_8)); + stubFiles.add(lce.toString()); + + // ConstantCallSite + Path ccs = invokePkg.resolve("ConstantCallSite.java"); + Files.write(ccs, ("package java.lang.invoke;\n" + + "public class ConstantCallSite extends CallSite {\n" + + " public ConstantCallSite(MethodHandle target) { }\n" + + " public final MethodHandle getTarget() { return null; }\n" + + " public final void setTarget(MethodHandle ignore) { }\n" + + "}").getBytes(StandardCharsets.UTF_8)); + stubFiles.add(ccs.toString()); + + return stubFiles; + } + + private void writeMissingHeadersAndImpls(Path srcRoot) throws Exception { + // java_lang_NullPointerException + Path npeHeader = srcRoot.resolve("java_lang_NullPointerException.h"); + if (!Files.exists(npeHeader)) { + String npeContent = "#ifndef __JAVA_LANG_NULLPOINTEREXCEPTION_H__\n" + + "#define __JAVA_LANG_NULLPOINTEREXCEPTION_H__\n" + + "#include \"cn1_globals.h\"\n" + + "JAVA_OBJECT __NEW_INSTANCE_java_lang_NullPointerException(CODENAME_ONE_THREAD_STATE);\n" + + "#endif\n"; + Files.write(npeHeader, npeContent.getBytes(StandardCharsets.UTF_8)); + } + + // java_lang_String + Path stringHeader = srcRoot.resolve("java_lang_String.h"); + if (!Files.exists(stringHeader)) { + String stringContent = "#ifndef __JAVA_LANG_STRING_H__\n" + + "#define __JAVA_LANG_STRING_H__\n" + + "#include \"cn1_globals.h\"\n" + + "extern struct clazz class__java_lang_String;\n" + + "extern struct clazz class_array2__java_lang_String;\n" + + "JAVA_OBJECT __NEW_ARRAY_java_lang_String(CODENAME_ONE_THREAD_STATE, JAVA_INT size);\n" + + "#endif\n"; + Files.write(stringHeader, stringContent.getBytes(StandardCharsets.UTF_8)); + } + + // java_lang_Class + Path classHeader = srcRoot.resolve("java_lang_Class.h"); + if (!Files.exists(classHeader)) { + String classHeaderContent = "#ifndef __JAVA_LANG_CLASS_H__\n#define __JAVA_LANG_CLASS_H__\n#include \"cn1_globals.h\"\n" + + "extern struct clazz class__java_lang_Class;\n" + + "#endif\n"; + Files.write(classHeader, classHeaderContent.getBytes(StandardCharsets.UTF_8)); + } + + // java_util_Objects + Path objectsHeader = srcRoot.resolve("java_util_Objects.h"); + if (!Files.exists(objectsHeader)) { + String headerContent = "#ifndef __JAVA_UTIL_OBJECTS_H__\n" + + "#define __JAVA_UTIL_OBJECTS_H__\n" + + "#include \"cn1_globals.h\"\n" + + "extern struct clazz class__java_util_Objects;\n" + + "JAVA_OBJECT java_util_Objects_requireNonNull___java_lang_Object_R_java_lang_Object(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj);\n" + + "#endif\n"; + Files.write(objectsHeader, headerContent.getBytes(StandardCharsets.UTF_8)); + } + + // java_lang_Object + Path objectHeader = srcRoot.resolve("java_lang_Object.h"); + // Overwrite or create + String objectContent = "#ifndef __JAVA_LANG_OBJECT_H__\n" + + "#define __JAVA_LANG_OBJECT_H__\n" + + "#include \"cn1_globals.h\"\n" + + "extern struct clazz class__java_lang_Object;\n" + + "void __FINALIZER_java_lang_Object(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj);\n" + + "void __GC_MARK_java_lang_Object(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj, JAVA_BOOLEAN force);\n" + + "void java_lang_Object___INIT____(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj);\n" + + "JAVA_OBJECT __NEW_java_lang_Object(CODENAME_ONE_THREAD_STATE);\n" + + "void __INIT_VTABLE_java_lang_Object(CODENAME_ONE_THREAD_STATE, void** vtable);\n" + + "#endif\n"; + Files.write(objectHeader, objectContent.getBytes(StandardCharsets.UTF_8)); + + + // Append implementations to runtime_stubs.c or create extra_stubs.c + Path extraStubs = srcRoot.resolve("extra_stubs.c"); + if (!Files.exists(extraStubs)) { + String stubs = "#include \"cn1_globals.h\"\n" + + "#include \"java_lang_NullPointerException.h\"\n" + + "#include \"java_lang_String.h\"\n" + + "#include \"java_lang_Class.h\"\n" + + "#include \"java_lang_Object.h\"\n" + + "#include \"java_util_Objects.h\"\n" + + "#include \n" + + "#include \n" + + "#include \n" + + "\n" + + "// class__java_lang_String defined in runtime_stubs.c\n" + + "struct clazz class_array2__java_lang_String = {0};\n" + + "// class__java_lang_Class defined in runtime_stubs.c\n" + + "struct clazz class__java_lang_Object = {0};\n" + + "struct clazz class__java_util_Objects = {0};\n" + + "\n" + + "JAVA_OBJECT __NEW_INSTANCE_java_lang_NullPointerException(CODENAME_ONE_THREAD_STATE) {\n" + + " fprintf(stderr, \"Allocating NullPointerException\\n\");\n" + + " fflush(stderr);\n" + + " return JAVA_NULL;\n" + + "}\n" + + "void __FINALIZER_java_lang_Object(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj) {}\n" + + "void __GC_MARK_java_lang_Object(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj, JAVA_BOOLEAN force) {}\n" + + "void java_lang_Object___INIT____(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj) {}\n" + + "JAVA_OBJECT __NEW_java_lang_Object(CODENAME_ONE_THREAD_STATE) {\n" + + " fprintf(stderr, \"__NEW_java_lang_Object called\\n\");\n" + + " fflush(stderr);\n" + + " struct JavaObjectPrototype* ptr = (struct JavaObjectPrototype*)malloc(sizeof(struct JavaObjectPrototype));\n" + + " if (ptr) {\n" + + " memset(ptr, 0, sizeof(struct JavaObjectPrototype));\n" + + " ptr->__codenameOneParentClsReference = &class__java_lang_Object;\n" + + " }\n" + + " return (JAVA_OBJECT)ptr;\n" + + "}\n" + + "void __INIT_VTABLE_java_lang_Object(CODENAME_ONE_THREAD_STATE, void** vtable) {}\n" + + "JAVA_OBJECT __NEW_ARRAY_java_lang_String(CODENAME_ONE_THREAD_STATE, JAVA_INT size) {\n" + + " return 0;\n" + + "}\n" + + "JAVA_OBJECT java_util_Objects_requireNonNull___java_lang_Object_R_java_lang_Object(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj) {\n" + + " if (obj == JAVA_NULL) {\n" + + " fprintf(stderr, \"requireNonNull failed\\n\");\n" + + " exit(1);\n" + + " }\n" + + " return obj;\n" + + "}\n" + + "void gcMarkObject(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj, JAVA_BOOLEAN force) {\n" + + " // Dummy implementation\n" + + "}\n"; + Files.write(extraStubs, stubs.getBytes(StandardCharsets.UTF_8)); + } + } + + private void writeRuntimeStubs(Path srcRoot) throws java.io.IOException { + Path objectHeader = srcRoot.resolve("java_lang_Object.h"); + if (!Files.exists(objectHeader)) { + String headerContent = "#ifndef __JAVA_LANG_OBJECT_H__\n" + + "#define __JAVA_LANG_OBJECT_H__\n" + + "#include \"cn1_globals.h\"\n" + + "#endif\n"; + Files.write(objectHeader, headerContent.getBytes(StandardCharsets.UTF_8)); + } + + Path stubs = srcRoot.resolve("runtime_stubs.c"); + if (Files.exists(stubs)) { + return; + } + String content = "#include \"cn1_globals.h\"\n" + + "#include \n" + + "#include \n" + + "#include \n" + + "#include \n" + + "#include \n" + + "\n" + + "static struct ThreadLocalData globalThreadData;\n" + + "static int runtimeInitialized = 0;\n" + + "\n" + + "static void initThreadState() {\n" + + " memset(&globalThreadData, 0, sizeof(globalThreadData));\n" + + " globalThreadData.blocks = calloc(CN1_MAX_STACK_CALL_DEPTH, sizeof(struct TryBlock));\n" + + " globalThreadData.threadObjectStack = calloc(CN1_MAX_OBJECT_STACK_DEPTH, sizeof(struct elementStruct));\n" + + " globalThreadData.pendingHeapAllocations = calloc(CN1_MAX_OBJECT_STACK_DEPTH, sizeof(void*));\n" + + " globalThreadData.callStackClass = calloc(CN1_MAX_STACK_CALL_DEPTH, sizeof(int));\n" + + " globalThreadData.callStackLine = calloc(CN1_MAX_STACK_CALL_DEPTH, sizeof(int));\n" + + " globalThreadData.callStackMethod = calloc(CN1_MAX_STACK_CALL_DEPTH, sizeof(int));\n" + + "}\n" + + "\n" + + "struct ThreadLocalData* getThreadLocalData() {\n" + + " if (!runtimeInitialized) {\n" + + " initThreadState();\n" + + " runtimeInitialized = 1;\n" + + " }\n" + + " return &globalThreadData;\n" + + "}\n" + + "\n" + + "JAVA_OBJECT codenameOneGcMalloc(CODENAME_ONE_THREAD_STATE, int size, struct clazz* parent) {\n" + + " JAVA_OBJECT obj = (JAVA_OBJECT)calloc(1, size);\n" + + " if (obj != JAVA_NULL) {\n" + + " obj->__codenameOneParentClsReference = parent;\n" + + " }\n" + + " return obj;\n" + + "}\n" + + "\n" + + "void codenameOneGcFree(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj) {\n" + + " free(obj);\n" + + "}\n" + + "\n" + + "JAVA_OBJECT* constantPoolObjects = NULL;\n" + + "\n" + + "void initConstantPool() {\n" + + " if (constantPoolObjects == NULL) {\n" + + " constantPoolObjects = calloc(32, sizeof(JAVA_OBJECT));\n" + + " }\n" + + "}\n" + + "\n" + + "void arrayFinalizerFunction(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT array) {\n" + + " (void)threadStateData;\n" + + " free(array);\n" + + "}\n" + + "\n" + + "void gcMarkArrayObject(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj, JAVA_BOOLEAN force) {\n" + + " (void)threadStateData;\n" + + " (void)obj;\n" + + " (void)force;\n" + + "}\n" + + "\n" + + "void** initVtableForInterface() {\n" + + " static void* table[1];\n" + + " return (void**)table;\n" + + "}\n" + + "\n" + + "struct clazz class_array1__JAVA_INT = {0};\n" + + "struct clazz class_array2__JAVA_INT = {0};\n" + + "struct clazz class_array1__JAVA_BOOLEAN = {0};\n" + + "struct clazz class_array1__JAVA_CHAR = {0};\n" + + "struct clazz class_array1__JAVA_FLOAT = {0};\n" + + "struct clazz class_array1__JAVA_DOUBLE = {0};\n" + + "struct clazz class_array1__JAVA_BYTE = {0};\n" + + "struct clazz class_array1__JAVA_SHORT = {0};\n" + + "struct clazz class_array1__JAVA_LONG = {0};\n" + + "\n" + + "static JAVA_OBJECT allocArrayInternal(CODENAME_ONE_THREAD_STATE, int length, struct clazz* type, int primitiveSize, int dim) {\n" + + " fprintf(stderr, \"allocArrayInternal length=%d type=%p\\n\", length, type); fflush(stderr);\n" + + " struct JavaArrayPrototype* arr = (struct JavaArrayPrototype*)calloc(1, sizeof(struct JavaArrayPrototype));\n" + + " arr->__codenameOneParentClsReference = type;\n" + + " arr->length = length;\n" + + " arr->dimensions = dim;\n" + + " arr->primitiveSize = primitiveSize;\n" + + " if (length > 0) {\n" + + " int elementSize = primitiveSize > 0 ? primitiveSize : sizeof(JAVA_OBJECT);\n" + + " arr->data = calloc((size_t)length, (size_t)elementSize);\n" + + " }\n" + + " return (JAVA_OBJECT)arr;\n" + + "}\n" + + "\n" + + "JAVA_OBJECT allocArray(CODENAME_ONE_THREAD_STATE, int length, struct clazz* type, int primitiveSize, int dim) {\n" + + " return allocArrayInternal(threadStateData, length, type, primitiveSize, dim);\n" + + "}\n" + + "\n" + + "JAVA_OBJECT alloc2DArray(CODENAME_ONE_THREAD_STATE, int length1, int length2, struct clazz* parentType, struct clazz* childType, int primitiveSize) {\n" + + " struct JavaArrayPrototype* outer = (struct JavaArrayPrototype*)allocArrayInternal(threadStateData, length1, parentType, sizeof(JAVA_OBJECT), 2);\n" + + " JAVA_OBJECT* rows = (JAVA_OBJECT*)outer->data;\n" + + " for (int i = 0; i < length1; i++) {\n" + + " rows[i] = allocArrayInternal(threadStateData, length2, childType, primitiveSize, 1);\n" + + " }\n" + + " return (JAVA_OBJECT)outer;\n" + + "}\n" + + "\n" + + "void initMethodStack(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, int stackSize, int localsStackSize, int classNameId, int methodNameId) {\n" + + " (void)__cn1ThisObject;\n" + + " (void)stackSize;\n" + + " (void)classNameId;\n" + + " (void)methodNameId;\n" + + " threadStateData->threadObjectStackOffset += localsStackSize;\n" + + "}\n" + + "\n" + + "void releaseForReturn(CODENAME_ONE_THREAD_STATE, int cn1LocalsBeginInThread) {\n" + + " fprintf(stderr, \"releaseForReturn locals=%d\\n\", cn1LocalsBeginInThread); fflush(stderr);\n" + + " threadStateData->threadObjectStackOffset = cn1LocalsBeginInThread;\n" + + "}\n" + + "\n" + + "void releaseForReturnInException(CODENAME_ONE_THREAD_STATE, int cn1LocalsBeginInThread, int methodBlockOffset) {\n" + + " (void)methodBlockOffset;\n" + + " releaseForReturn(threadStateData, cn1LocalsBeginInThread);\n" + + "}\n" + + "\n" + + "void monitorEnter(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj) { fprintf(stderr, \"monitorEnter %p\\n\", obj); fflush(stderr); }\n" + + "\n" + + "void monitorExit(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj) { fprintf(stderr, \"monitorExit %p\\n\", obj); fflush(stderr); }\n" + + "\n" + + "void monitorEnterBlock(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj) { fprintf(stderr, \"monitorEnterBlock %p\\n\", obj); fflush(stderr); }\n" + + "\n" + + "void monitorExitBlock(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj) { fprintf(stderr, \"monitorExitBlock %p\\n\", obj); fflush(stderr); }\n" + + "\n" + + "struct elementStruct* pop(struct elementStruct** sp) {\n" + + " (*sp)--;\n" + + " return *sp;\n" + + "}\n" + + "\n" + + "void popMany(CODENAME_ONE_THREAD_STATE, int count, struct elementStruct** sp) {\n" + + " while (count-- > 0) {\n" + + " (*sp)--;\n" + + " }\n" + + "}\n" + + "\n" + + "void throwException(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj) {\n" + + " fprintf(stderr, \"Exception thrown! obj=%p\\n\", obj);\n" + + " fflush(stderr);\n" + + " exit(1);\n" + + "}\n" + + "\n" + + "struct clazz class__java_lang_Class = {0};\n" + + "struct clazz class__java_lang_String = {0};\n" + + "int currentGcMarkValue = 1;\n"; + + Files.write(stubs, content.getBytes(StandardCharsets.UTF_8)); + } +} From 457f2fbb9837d418180391faf526fe3abed3cae6 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 10:38:37 +0000 Subject: [PATCH 2/2] Add support for invokedynamic in ByteCodeTranslator for Java 8 lambdas and method references. - Implemented `visitInvokeDynamicInsn` in `Parser.java` to translate lambda expressions and method references into static proxy classes. - Added `LambdaIntegrationTest.java` in `vm/tests` to verify end-to-end compilation and execution of Java 8 lambda code translated to C. - Handled generation of necessary stubs (`java.lang.invoke`, `java.util.Objects`, `virtual_java_lang_Object_getClass`) in integration tests to support method references and implicit null checks. - Ensured generated lambda classes are registered to prevent dead code elimination. --- vm/JavaAPI/pom.xml | 4 ++-- .../tools/translator/LambdaIntegrationTest.java | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/vm/JavaAPI/pom.xml b/vm/JavaAPI/pom.xml index 6110119833..a74120c9bb 100644 --- a/vm/JavaAPI/pom.xml +++ b/vm/JavaAPI/pom.xml @@ -21,8 +21,8 @@ maven-compiler-plugin 3.11.0 - 1.8 - 1.8 + 1.5 + 1.5 -Xlint:-options diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/LambdaIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/LambdaIntegrationTest.java index 41e379a0b0..cd90c36177 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/LambdaIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/LambdaIntegrationTest.java @@ -322,8 +322,10 @@ private void writeMissingHeadersAndImpls(Path srcRoot) throws Exception { "void java_lang_Object___INIT____(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj);\n" + "JAVA_OBJECT __NEW_java_lang_Object(CODENAME_ONE_THREAD_STATE);\n" + "void __INIT_VTABLE_java_lang_Object(CODENAME_ONE_THREAD_STATE, void** vtable);\n" + + "JAVA_OBJECT java_lang_Object_getClass___R_java_lang_Class(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj);\n" + + "JAVA_OBJECT virtual_java_lang_Object_getClass___R_java_lang_Class(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj);\n" + "#endif\n"; - Files.write(objectHeader, objectContent.getBytes(StandardCharsets.UTF_8)); + Files.write(objectHeader, objectContent.getBytes(StandardCharsets.UTF_8)); // Append implementations to runtime_stubs.c or create extra_stubs.c @@ -376,6 +378,12 @@ private void writeMissingHeadersAndImpls(Path srcRoot) throws Exception { "}\n" + "void gcMarkObject(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj, JAVA_BOOLEAN force) {\n" + " // Dummy implementation\n" + + "}\n" + + "JAVA_OBJECT java_lang_Object_getClass___R_java_lang_Class(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj) {\n" + + " return JAVA_NULL; // Stub\n" + + "}\n" + + "JAVA_OBJECT virtual_java_lang_Object_getClass___R_java_lang_Class(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj) {\n" + + " return java_lang_Object_getClass___R_java_lang_Class(threadStateData, obj);\n" + "}\n"; Files.write(extraStubs, stubs.getBytes(StandardCharsets.UTF_8)); }