diff --git a/classpath/java/lang/invoke/LambdaMetafactory.java b/classpath/java/lang/invoke/LambdaMetafactory.java index a4d0b0e003..4eb5cf771a 100644 --- a/classpath/java/lang/invoke/LambdaMetafactory.java +++ b/classpath/java/lang/invoke/LambdaMetafactory.java @@ -33,9 +33,17 @@ import avian.Assembler; import avian.ConstantPool.PoolEntry; import avian.SystemClassLoader; +// To understand what this is all about, please read: +// +// http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html + public class LambdaMetafactory { private static int nextNumber = 0; + public static final int FLAG_SERIALIZABLE = 1; + public static final int FLAG_MARKERS = 2; + public static final int FLAG_BRIDGES = 4; + private static Class resolveReturnInterface(MethodType type) { int index = 1; byte[] s = type.spec; @@ -212,13 +220,15 @@ public class LambdaMetafactory { new MethodHandle(implementationClass, implementationName, implementationSpec, - implementationKind)); + implementationKind), + emptyInterfaceList); } private static byte[] makeLambda(String invokedName, MethodType invokedType, MethodType methodType, - MethodHandle methodImplementation) + MethodHandle methodImplementation, + Class[] interfaces) { String className; { int number; @@ -230,8 +240,12 @@ public class LambdaMetafactory { List pool = new ArrayList(); - int interfaceIndex = ConstantPool.addClass - (pool, invokedType.returnType().getName().replace('.', '/')); + int[] interfaceIndexes = new int[interfaces.length + 1]; + interfaceIndexes[0] = ConstantPool.addClass(pool, invokedType.returnType().getName().replace('.', '/')); + for (int i = 0; i < interfaces.length; i++) { + String name = interfaces[i].getName().replace('.', '/'); + interfaceIndexes[i + 1] = ConstantPool.addClass(pool, name); + } List fieldTable = new ArrayList(); @@ -280,7 +294,7 @@ public class LambdaMetafactory { ByteArrayOutputStream out = new ByteArrayOutputStream(); try { Assembler.writeClass - (out, pool, nameIndex, superIndex, new int[] { interfaceIndex }, + (out, pool, nameIndex, superIndex, interfaceIndexes, fieldTable.toArray(new FieldData[fieldTable.size()]), methodTable.toArray(new MethodData[methodTable.size()])); } catch (IOException e) { @@ -292,6 +306,24 @@ public class LambdaMetafactory { return out.toByteArray(); } + private static CallSite makeCallSite(MethodType invokedType, byte[] classData) throws AssertionError { + try { + return new CallSite + (new MethodHandle + (MethodHandle.REF_invokeStatic, invokedType.loader, Classes.toVMMethod + (avian.SystemClassLoader.getClass + (avian.Classes.defineVMClass + (invokedType.loader, classData, 0, classData.length)) + .getMethod("make", invokedType.parameterArray())))); + } catch (NoSuchMethodException e) { + AssertionError error = new AssertionError(); + error.initCause(e); + throw error; + } + } + + private static final Class[] emptyInterfaceList = new Class[] {}; + public static CallSite metafactory(MethodHandles.Lookup caller, String invokedName, MethodType invokedType, @@ -300,30 +332,79 @@ public class LambdaMetafactory { MethodType instantiatedMethodType) throws LambdaConversionException { - byte[] classData = makeLambda(invokedName, invokedType, methodType, methodImplementation); - - try { - return new CallSite - (new MethodHandle - (MethodHandle.REF_invokeStatic, invokedType.loader, Classes.toVMMethod - (avian.SystemClassLoader.getClass - (avian.Classes.defineVMClass - (invokedType.loader, classData, 0, classData.length)) - .getMethod("make", invokedType.parameterArray())))); - } catch (NoSuchMethodException e) { - AssertionError error = new AssertionError(); - error.initCause(e); - throw error; - } + byte[] classData = makeLambda(invokedName, invokedType, methodType, methodImplementation, emptyInterfaceList); + return makeCallSite(invokedType, classData); } public static CallSite altMetafactory(MethodHandles.Lookup caller, String invokedName, MethodType invokedType, - Object... args) - throws LambdaConversionException - { - // todo: handle flags - return metafactory(caller, invokedName, invokedType, (MethodType) args[0], (MethodHandle) args[1], (MethodType) args[2]); + Object... args) throws LambdaConversionException { + // See openjdk8/jdk/src/share/classes/java/lang/invoke/LambdaMetafactory.java + // Behaves as if the prototype is like this: + // + // CallSite altMetafactory( + // MethodHandles.Lookup caller, + // String invokedName, + // MethodType invokedType, + // MethodType methodType, + // MethodHandle methodImplementation, + // MethodType instantiatedMethodType, + // int flags, + // int markerInterfaceCount, // IF flags has MARKERS set + // Class... markerInterfaces, // IF flags has MARKERS set + // int bridgeCount, // IF flags has BRIDGES set + // MethodType... bridges // IF flags has BRIDGES set + // ) + MethodType methodType = (MethodType) args[0]; + MethodHandle methodImplementation = (MethodHandle) args[1]; + + int flags = (Integer) args[3]; + boolean serializable = (flags & FLAG_SERIALIZABLE) != 0; + + // Marker interfaces are added to a lambda when they're written like this: + // + // Runnable r = (Runnable & Serializable) () -> foo() + // + // The intersection type in the cast here indicates to the compiler what interfaces + // the generated lambda class should implement. Because a lambda has (by definition) + // one method only, it is meaningless for these interfaces to contain anything, thus + // they are only allowed to be empty marker interfaces. In practice the Serializable + // interface is handled specially and the use of markers is extremely rare. Adding + // support would be easy though. + if ((flags & FLAG_MARKERS) != 0) + throw new UnsupportedOperationException("Marker interfaces on lambdas are not supported on Avian yet. Sorry."); + + // In some cases there is a mismatch between what the JVM type system supports and + // what the Java language supports. In other cases the type of a lambda expression + // may not perfectly match the functional interface which represents it. Consider the + // following case: + // + // interface I { void foo(Integer i, String s1, Strings s2) } + // class Foo { static void m(Number i, Object... rest) {} } + // + // I lambda = Foo::m + // + // This is allowed by the Java language, even though the interface representing the + // lambda specifies three specific arguments and the method implementing the lambda + // uses varargs and a different type signature. Behind the scenes the compiler generates + // a "bridge" method that does the adaptation. + // + // You can learn more here: http://www.oracle.com/technetwork/java/jvmls2013heid-2013922.pdf + // and here: http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html + if ((flags & FLAG_BRIDGES) != 0) { + int bridgeCount = (Integer) args[4]; + if (bridgeCount > 0) + throw new UnsupportedOperationException("A lambda that requires bridge methods was used, this is not yet supported by Avian. Sorry."); + } + + // TODO: This is not necessary if the function type interface is already inheriting + // from Serializable. + Class[] interfaces = new Class[serializable ? 1 : 0]; + if (serializable) + interfaces[0] = java.io.Serializable.class; + + byte[] classData = makeLambda(invokedName, invokedType, methodType, methodImplementation, interfaces); + return makeCallSite(invokedType, classData); } } diff --git a/src/machine.cpp b/src/machine.cpp index c2d43a503c..af00364cad 100644 --- a/src/machine.cpp +++ b/src/machine.cpp @@ -6032,13 +6032,26 @@ GcJclass* getDeclaringClass(Thread* t, GcClass* c) return 0; } +// Called when interpreting invokedynamic. `invocation` points to +// static data in the bootstrap method table, which in turn points to +// a bootstrap method and stores additional data to be passed to +// it. `resolveDynamic` will then call this bootstrap method after +// resolving the arguments as required. The called method is assumed +// to be a lambda `metafactory` or `altMetafactory`. +// +// Note that capture/bridging etc happens within the bootstrap method, +// this is just the code that dispatches to it. +// +// Returns the CallSite returned by the bootstrap method. GcCallSite* resolveDynamic(Thread* t, GcInvocation* invocation) { PROTECT(t, invocation); + // Use the invocation's Class to get the bootstrap method table and get a classloader. GcClass* c = invocation->class_(); PROTECT(t, c); + // First element points to the bootstrap method. The rest are static data passed to the BSM. GcCharArray* bootstrapArray = cast( t, cast(t, c->addendum()->bootstrapMethodTable()) @@ -6046,6 +6059,7 @@ GcCallSite* resolveDynamic(Thread* t, GcInvocation* invocation) PROTECT(t, bootstrapArray); + // Resolve the bootstrap method itself. GcMethod* bootstrap = cast(t, resolve(t, c->loader(), @@ -6055,22 +6069,29 @@ GcCallSite* resolveDynamic(Thread* t, GcInvocation* invocation) GcNoSuchMethodError::Type)); PROTECT(t, bootstrap); + // Caller context info to be passed to the bootstrap method. GcLookup* lookup = makeLookup(t, c, ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC); PROTECT(t, lookup); + // The name of the linked-to method. GcByteArray* nameBytes = invocation->template_()->name(); GcString* name = t->m->classpath->makeString(t, nameBytes, 0, nameBytes->length() - 1); PROTECT(t, name); + // This is the type of the linked-to method (e.g. lambda). GcMethodType* type = makeMethodType( t, c->loader(), invocation->template_()->spec(), 0, 0, 0); PROTECT(t, type); + // `array` stores either + // 1. All the arguments to be passed to the bootstrap method in the case of `metafactory` + // 2. The vararg object array to be passed to `altMetafactory` GcArray* array = makeArray(t, bootstrap->parameterCount()); PROTECT(t, array); + // These are common arguments to metafactory and altMetafactory unsigned argument = 0; array->setBodyElement(t, argument++, lookup); array->setBodyElement(t, argument++, name); @@ -6079,16 +6100,28 @@ GcCallSite* resolveDynamic(Thread* t, GcInvocation* invocation) THREAD_RUNTIME_ARRAY(t, char, specBuffer, bootstrap->spec()->length()); const char* spec; + // `argArray` stores the final arguments to be passed to the bootstrap method. + // Later in this function we iterate through the method signature + + // bootstrap array and resolve the arguments as required into `array`. + // + // In the case of a `metafactory` call: + // `argArray = [caller, invokedName, invokedType, methodType, methodImplementation, instantiatedType]` + // `array = argArray` + // + // In the case of an `altMetafactory` call: + // `argArray = [caller, invokedName, invokedType, array]` + // `array = [methodType, methodImplementation, instantiatedType, flags, ...]` GcArray* argArray = array; PROTECT(t, argArray); + // Check if the bootstrap method's signature matches that of an altMetafactory if (::strcmp(reinterpret_cast(bootstrap->spec()->body().begin()), "(Ljava/lang/invoke/MethodHandles$Lookup;" "Ljava/lang/String;" "Ljava/lang/invoke/MethodType;" "[Ljava/lang/Object;)" "Ljava/lang/invoke/CallSite;") == 0) { - // LambdaMetaFactory.altMetafactory + // If so, create a new array to store the varargs in, and hardcode the BSM signature. array = makeArray(t, bootstrapArray->length() - 1); spec = "(Ljava/lang/invoke/MethodHandles$Lookup;" "Ljava/lang/String;" @@ -6103,6 +6136,9 @@ GcCallSite* resolveDynamic(Thread* t, GcInvocation* invocation) "[Ljava/lang/invoke/MethodType;" ")Ljava/lang/invoke/CallSite;"; } else if (bootstrap->parameterCount() == 2 + bootstrapArray->length()) { + // We're calling the simpler `metafactory`. 2 + bootstrapArray->length() is the + // arguments to the bootstrap method (bootstrapArray->length() - 1), plus the 3 static + // arguments (lookup, name, type). memcpy(RUNTIME_ARRAY_BODY(specBuffer), bootstrap->spec()->body().begin(), bootstrap->spec()->length()); @@ -6113,16 +6149,24 @@ GcCallSite* resolveDynamic(Thread* t, GcInvocation* invocation) MethodSpecIterator it(t, spec); + // Skip over the already handled 3 arguments. for (unsigned i = 0; i < argument; ++i) it.next(); + // If we're calling altMetafactory then we reset the argument + // offset, because we are filling the vararg array instead of the + // final argument array. if (argArray != array) { argument = 0; } + // `i` iterates through the bootstrap arguments (the +1 is because we skip + // the boostrap method's name), `it` iterates through the corresponding types + // in the method signature unsigned i = 0; while (i + 1 < bootstrapArray->length() && it.hasNext()) { const char* p = it.next(); + switch (*p) { case 'L': { const char* const methodType = "Ljava/lang/invoke/MethodType;"; @@ -6198,10 +6242,12 @@ GcCallSite* resolveDynamic(Thread* t, GcInvocation* invocation) ? 0 : makeMethodHandle(t, REF_invokeSpecial, c->loader(), bootstrap, 0); + // If we're calling altMetafactory we set the fourth argument to the vararg array. if (argArray != array) { argArray->setBodyElement(t, 3, array); } + // Finally we make the bootstrap call. return cast( t, t->m->processor->invokeArray(t, bootstrap, handle, argArray)); } diff --git a/test/InvokeDynamic.java b/test/InvokeDynamic.java index e312e78d82..6d6e2842aa 100644 --- a/test/InvokeDynamic.java +++ b/test/InvokeDynamic.java @@ -41,6 +41,32 @@ public class InvokeDynamic { } } + private interface Foo extends java.io.Serializable { + void someFunction(Integer a, Integer b, String s); + } + + private interface UnboxedSerializable extends java.io.Serializable { + int add(int a, int b); + } + + private interface Unboxed { + int add(int a, int b); + } + + private void requiresBridge(Number a, Object... rest) { + String s = "" + a; + for (Object r : rest) { + s += r; + } + } + + private static Integer addBoxed(Integer a, Integer b) { + return a + b; + } + + private interface Marker { + } + private void test() { { int c = 2; Operation op = (a, b) -> ((a + b) * c) - foo; @@ -62,5 +88,24 @@ public class InvokeDynamic { expect(s.get().first == 42L); expect(s.get().second == 3.14D); } + + { Foo s = this::requiresBridge; + s.someFunction(1, 2, ""); + } + + // This abort()s in machine.cpp + // { Foo s = (Foo & Marker) this::requiresBridge; + // s.someFunction(1, 2, ""); + // } + + // NPE + // { UnboxedSerializable s = InvokeDynamic::addBoxed; + // expect(s.add(1, 2) == 3); + // } + + // NPE + // { Unboxed s = InvokeDynamic::addBoxed; + // expect(s.add(1, 2) == 3); + // } } }