Skip to content

Commit fc36b02

Browse files
Support for invokedynamic (Java 8 Lambdas) in ByteCodeTranslator (#4279)
* 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. * 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. --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
1 parent f12fdd6 commit fc36b02

File tree

2 files changed

+722
-0
lines changed

2 files changed

+722
-0
lines changed

vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.objectweb.asm.Label;
3636
import org.objectweb.asm.MethodVisitor;
3737
import org.objectweb.asm.Opcodes;
38+
import org.objectweb.asm.Type;
3839
import org.objectweb.asm.TypePath;
3940
import org.objectweb.asm.commons.JSRInlinerAdapter;
4041

@@ -49,6 +50,7 @@ public class Parser extends ClassVisitor {
4950
private String clsName;
5051
private static String[] nativeSources;
5152
private static List<ByteCodeClass> classes = new ArrayList<ByteCodeClass>();
53+
private int lambdaCounter;
5254
public static void cleanup() {
5355
nativeSources = null;
5456
classes.clear();
@@ -836,6 +838,170 @@ public void visitJumpInsn(int opcode, Label label) {
836838

837839
@Override
838840
public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
841+
if ("java/lang/invoke/LambdaMetafactory".equals(bsm.getOwner()) &&
842+
("metafactory".equals(bsm.getName()) || "altMetafactory".equals(bsm.getName()))) {
843+
844+
// 1. Generate a unique class name for the lambda
845+
String lambdaClassName = clsName + "_lambda_" + (lambdaCounter++);
846+
847+
// 2. Create the ByteCodeClass for the lambda
848+
ByteCodeClass lambdaClass = new ByteCodeClass(lambdaClassName, lambdaClassName.replace('_', '/'));
849+
lambdaClass.setBaseClass("java/lang/Object");
850+
851+
// The interface implemented is the return type of the invokedynamic descriptor
852+
Type invokedType = Type.getMethodType(desc);
853+
Type interfaceType = invokedType.getReturnType();
854+
lambdaClass.setBaseInterfaces(new String[]{interfaceType.getInternalName()});
855+
856+
// 3. Add fields for captured arguments
857+
Type[] capturedArgs = invokedType.getArgumentTypes();
858+
for (int i = 0; i < capturedArgs.length; i++) {
859+
String fieldName = "arg$" + (i + 1);
860+
String fieldDesc = capturedArgs[i].getDescriptor();
861+
ByteCodeField field = new ByteCodeField(lambdaClassName, Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, fieldName, fieldDesc, null, null);
862+
lambdaClass.addField(field);
863+
}
864+
865+
// 4. Add Constructor
866+
StringBuilder ctorDesc = new StringBuilder("(");
867+
for (Type t : capturedArgs) {
868+
ctorDesc.append(t.getDescriptor());
869+
}
870+
ctorDesc.append(")V");
871+
872+
BytecodeMethod ctor = new BytecodeMethod(lambdaClassName, Opcodes.ACC_PUBLIC, "<init>", ctorDesc.toString(), null, null);
873+
lambdaClass.addMethod(ctor);
874+
875+
// Constructor body (we need to generate instructions manually)
876+
// ALOAD 0
877+
// INVOKESPECIAL java/lang/Object.<init>
878+
// ... assign fields ...
879+
// RETURN
880+
881+
ctor.addInstruction(Opcodes.ALOAD); // 25
882+
ctor.addVariableOperation(Opcodes.ALOAD, 0);
883+
ctor.addInvoke(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
884+
885+
int varIndex = 1;
886+
for (int i = 0; i < capturedArgs.length; i++) {
887+
ctor.addInstruction(Opcodes.ALOAD);
888+
ctor.addVariableOperation(Opcodes.ALOAD, 0); // this
889+
890+
Type t = capturedArgs[i];
891+
int opcode = t.getOpcode(Opcodes.ILOAD); // correct load opcode for type
892+
ctor.addVariableOperation(opcode, varIndex);
893+
varIndex += t.getSize();
894+
895+
String fieldName = "arg$" + (i + 1);
896+
ctor.addField(lambdaClass, Opcodes.PUTFIELD, lambdaClassName, fieldName, t.getDescriptor());
897+
}
898+
ctor.addInstruction(Opcodes.RETURN);
899+
ctor.setMaxes(varIndex + 1, varIndex); // Approximate maxes
900+
901+
902+
// 5. Implement the interface method
903+
Type samMethodType = (Type) bsmArgs[0];
904+
Handle implMethod = (Handle) bsmArgs[1];
905+
Type instantiatedMethodType = (Type) bsmArgs[2];
906+
907+
String samMethodName = name; // Name from invokedynamic
908+
String samMethodDesc = samMethodType.getDescriptor(); // Signature from BSM arg 0
909+
910+
BytecodeMethod interfaceMethod = new BytecodeMethod(lambdaClassName, Opcodes.ACC_PUBLIC, samMethodName, samMethodDesc, null, null);
911+
lambdaClass.addMethod(interfaceMethod);
912+
913+
// Method Body:
914+
// Load captured arguments from fields
915+
// Load method arguments
916+
// Invoke implMethod
917+
// Return result
918+
919+
// Handle Constructor Reference (special case)
920+
boolean isCtorRef = (implMethod.getTag() == Opcodes.H_NEWINVOKESPECIAL);
921+
if (isCtorRef) {
922+
interfaceMethod.addTypeInstruction(Opcodes.NEW, implMethod.getOwner());
923+
interfaceMethod.addInstruction(Opcodes.DUP);
924+
}
925+
926+
// Load captured args
927+
for (int i = 0; i < capturedArgs.length; i++) {
928+
interfaceMethod.addInstruction(Opcodes.ALOAD);
929+
interfaceMethod.addVariableOperation(Opcodes.ALOAD, 0);
930+
String fieldName = "arg$" + (i + 1);
931+
interfaceMethod.addField(lambdaClass, Opcodes.GETFIELD, lambdaClassName, fieldName, capturedArgs[i].getDescriptor());
932+
}
933+
934+
// Load method args
935+
Type[] samArgs = samMethodType.getArgumentTypes();
936+
int localIndex = 1;
937+
for (Type t : samArgs) {
938+
interfaceMethod.addVariableOperation(t.getOpcode(Opcodes.ILOAD), localIndex);
939+
localIndex += t.getSize();
940+
}
941+
942+
// Invoke implMethod
943+
int invokeOpcode;
944+
switch (implMethod.getTag()) {
945+
case Opcodes.H_INVOKESTATIC: invokeOpcode = Opcodes.INVOKESTATIC; break;
946+
case Opcodes.H_INVOKEVIRTUAL: invokeOpcode = Opcodes.INVOKEVIRTUAL; break;
947+
case Opcodes.H_INVOKEINTERFACE: invokeOpcode = Opcodes.INVOKEINTERFACE; break;
948+
case Opcodes.H_INVOKESPECIAL: invokeOpcode = Opcodes.INVOKESPECIAL; break;
949+
case Opcodes.H_NEWINVOKESPECIAL: invokeOpcode = Opcodes.INVOKESPECIAL; break;
950+
default: invokeOpcode = Opcodes.INVOKESTATIC; // Fallback
951+
}
952+
953+
if (isCtorRef) {
954+
interfaceMethod.addInvoke(Opcodes.INVOKESPECIAL, implMethod.getOwner(), implMethod.getName(), implMethod.getDesc(), false);
955+
} else {
956+
interfaceMethod.addInvoke(invokeOpcode, implMethod.getOwner(), implMethod.getName(), implMethod.getDesc(), implMethod.isInterface());
957+
}
958+
959+
// Return
960+
Type returnType = samMethodType.getReturnType();
961+
interfaceMethod.addInstruction(returnType.getOpcode(Opcodes.IRETURN));
962+
interfaceMethod.setMaxes(20, 20); // Approximation
963+
964+
965+
// 6. Add static factory method
966+
String factoryMethodName = "lambda$factory";
967+
String factoryDesc = desc; // The desc of invokedynamic is (CapturedArgs)Interface.
968+
969+
// We want factory to be (CapturedArgs)LambdaClass (to match NEW output but wrapped)
970+
// Actually, replacing invokedynamic with INVOKESTATIC means the return type on stack should match
971+
// what invokedynamic promised, which is the Interface.
972+
// Our factory returns LambdaClass, which implements Interface. So it's assignment compatible.
973+
// However, the method signature in C needs to return an object pointer anyway.
974+
975+
// Let's make the factory return the class type explicitly in signature
976+
String factoryRetType = "L" + lambdaClassName + ";";
977+
String actualFactoryDesc = desc.substring(0, desc.lastIndexOf(')') + 1) + factoryRetType;
978+
979+
BytecodeMethod factory = new BytecodeMethod(lambdaClassName, Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, factoryMethodName, actualFactoryDesc, null, null);
980+
lambdaClass.addMethod(factory);
981+
982+
factory.addTypeInstruction(Opcodes.NEW, lambdaClassName);
983+
factory.addInstruction(Opcodes.DUP);
984+
985+
// Load factory arguments (captured args)
986+
localIndex = 0; // Static method
987+
for (Type t : capturedArgs) {
988+
factory.addVariableOperation(t.getOpcode(Opcodes.ILOAD), localIndex);
989+
localIndex += t.getSize();
990+
}
991+
992+
factory.addInvoke(Opcodes.INVOKESPECIAL, lambdaClassName, "<init>", ctorDesc.toString(), false);
993+
factory.addInstruction(Opcodes.ARETURN);
994+
factory.setMaxes(localIndex + 2, localIndex);
995+
996+
// 7. Register the new class
997+
classes.add(lambdaClass);
998+
999+
// 8. Replace invokedynamic with INVOKESTATIC to factory
1000+
mtd.addInvoke(Opcodes.INVOKESTATIC, lambdaClassName, factoryMethodName, actualFactoryDesc, false);
1001+
1002+
return;
1003+
}
1004+
8391005
super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
8401006
}
8411007

0 commit comments

Comments
 (0)