Skip to main content

JSR 292 goodness: Lambda to SAM type conversion using invokedynamic

Posted by forax on January 4, 2011 at 5:25 PM PST

Happy new year !

We made some interresting progress toward JSR 292 completion. As John already blogged, the latest build of jdk7 (b123) comes with a nearly up to date version of JSR 292 API. This build introduces a new mechanism that allow to send constant arguments to the bootstrap method of an invokedynamic. This blog entry is about how to use this feature to implement lambda to SAM type conversion.

Let suppose I want to translate this following code to bytecode.

  Comparator<Object, Object> c = #{  o1, o2 -> 1 };

Currently (*), the prototype compiler of the lambda project, first create a static method (lambda$1) corresponding to the lambda then create a MethodHandle on this method. This method handle is after translated into an instance of class implementing the interface using a java.lang.reflect.Proxy.
Here, the lambda is not bound to any local arguments so the instance of the class Comparator can be created once. Using a trick similar to the one use to implement the DCL Pattern, it can be implemented using invokedynamic.

With invokedynamic it's simpler. But because there is no Java syntax for invokedynamic, I can't explain the translation using real Java code but in pseudo Java code, it's something like that:

  Comparator c = (Comparator) invokedynamic [Lambdas#asSamBSM, #lambda$1, Comparator.class] ();

Or using ASM 4, the future version of ASM that supports invokedynamic's boostrap arguments:

    mv.visitIndyMethodInsn("_", "()Ljava/util/Comparator;",
        new MHandle(MHandle.REF_invokeStatic, "Lambdas", "asSamBSM",
            "(Ljava/dyn/MethodHandles$Lookup;Ljava/lang/String;Ljava/dyn/MethodType;[Ljava/lang/Object;)Ljava/dyn/CallSite;"),
        new Object[] {
            new MHandle(MHandle.REF_invokeStatic, "LambdaTest", "lambda$1",
                 "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)I"),
            Type.getObjectType("java/util/Comparator")
            });
    mv.visitVarInsn(ASTORE, 1);

And the code of the bootstrap method is

public class Lambdas {
  public static CallSite asSamBSM(Lookup lookup, String name, MethodType methodType, Object[] bsmArgs) {
    MethodHandle lambda = (MethodHandle)bsmArgs[0];
    Class<?> samInterface = (Class<?>)bsmArgs[1];
   
    if (methodType.returnType() != samInterface) {
      throw new InvokeDynamicBootstrapError("asSam incompatible return type "+methodType);
    }
   
    int parameterCount = methodType.parameterCount();
    if (parameterCount == 0) {  // constant case
      Object samInstance = MethodHandles.asInstance(lambda, samInterface);
      return new ConstantCallSite(
          MethodHandles.constant(samInterface, samInstance));
    }
   
    if (parameterCount == 1) {  // bind case
      MethodHandle combiner = BIND_TO.bindTo(lambda);
      MethodHandle target = MethodHandles.insertArguments(AS_INSTANCE, 1, samInterface);
      target = MethodHandles.dropArguments(target, 1, methodType.parameterType(0));
     
      return new ConstantCallSite(
          MethodHandles.foldArguments(target, combiner).asType(methodType));
    }
   
    throw new InvokeDynamicBootstrapError("asSam incompatible method type "+methodType);
  }
 
  private static final MethodHandle AS_INSTANCE;
  private static final MethodHandle BIND_TO;
  static {
    try {
      Lookup lookup = MethodHandles.publicLookup();
      AS_INSTANCE = lookup.findStatic(MethodHandles.class, "asInstance",
          MethodType.methodType(Object.class, MethodHandle.class, Class.class));
      BIND_TO = lookup.findVirtual(MethodHandle.class, "bindTo",
          MethodType.methodType(MethodHandle.class, Object.class));
    } catch (NoAccessException e) {
      throw new LinkageError("linkage error", e);
    }
  }
}

 

The main advantage of this code compared to the code currently emitted by the compiler prototype of lambda is that SAM convertions of a constant method handle are computed once on demand.

cheers,
Rémi

* Update: in fact, as pointed by Tomasz Kowalczewski, the compiler prototype code was changed 3 month ago to use a translation using inner classes.

Related Topics >>

Comments

JSR 292 goodness: Lambda to

I'm sorrybut I don't get it.
>With invokedynamic it's simpler. But because there is no Java syntax for invokedynamic ...
Why there is no Java syntax for invokedynamic? Is it planned for JDK 7 or 8. It's great to have vm support but to start using invokedynamic you need to have java language support.
Could you please comment on this?

JSR 292 goodness: Lambda to

Sorry for the late answer.
It's planned for JDK 8 because of its interraction with lambdas.
Rémi