Core Java

Java 7: A complete invokedynamic example

Another blog entry in my current Java 7 series. This time it’s dealing with invokedynamic, a new bytecode instruction on the JVM for method invocation. The invokedynamic instruction allows dynamic linkage between a call site and the receiver of the call.

That means you can link the class that is performing a method call to the class (and method) that is receiving the call at run-time. All the other JVM bytecode instructions for method invocation, like invokevirtual, hard-wire the target type information into your compilation, i.e. into your class file. Let’s look at an example.

Constant pool:
  #1 = Class              #2             //  com/schlimm/bytecode/examples/BytecodeExamples
  ...
  #42 = Class              #43            //  java/lang/String
  ...
  #65 = Methodref          #42.#66        //  java/lang/String.length:()I
  #66 = NameAndType        #67:#68        //  length:()I
  #67 = Utf8               length
  #68 = Utf8               ()I
  ...
{

...

  public void virtualMethodCall();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: ldc           #44                 // String Hello
         2: invokevirtual #65                 // Method java/lang/String.length:()I
         5: pop
         6: return
      LineNumberTable:
        line 31: 0
        line 32: 6
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       7     0  this   Lcom/schlimm/bytecode/examples/BytecodeExamples;
}

The bytecode snippet above shows an invokevirtual method call of java.lang.String -> length() in line 20. It refers to item 65 in the contsant pool table which is a MethodRef entry (see line 6). Items 42 and 66 in the constant pool table refer to the class and the method descriptor entries. As you can see, the target type and method of the invokevirtual call is completely resolved and hard-wired into the bytecode. Now, let’s return to invokedynamic!

It is important to notice that it is not possible to compile Java code into bytecode that contains an invokedynamic instruction. Java is statically typed. That means that Java performs type checking at compile time. Therefore, in Java, it is possible (and wanted!) to hard-wire all type information of method call receivers into the callers class file. The caller knows the type name of the call target, as demonstrated in our example above. The use of invokedynamic – on the other hand – enables the JVM to resolve exactly that type information at run-time. This is only required (and wanted!) for dynamic languages, such as JRuby or Rhino.

Now, suppose you want to implement a new language on the JVM that is dynamically typed. I am not suggesting you should invent *another* language on the JVM, but *suppose* you would, and *suppose* your new language should be dynamically typed. That would mean, in your new language, the linking between a caller and a receiver of a method call is performed at run-time. Since Java 7 this is possible on the bytecode level using the invokedynamic instruction.

Because I cannot create an invokedynamic instruction using a Java compiler, I will create a class file that contains invokedynamic myself. Once this class file is created I will run that class file’s main method using an ordinary java launcher. How can you create a class file without a compiler? This is possible by using bytecode manipulation frameworks like ASM or Javassist.

The following code snippet shows the SimpleDynamicInvokerGenerator that can generate a class file SimpleDynamicInvoker.class which contains an invokedynamic instruction.

public abstract class AbstractDynamicInvokerGenerator implements Opcodes {

 public byte[] dump(String dynamicInvokerClassName, String dynamicLinkageClassName, String bootstrapMethodName, String targetMethodDescriptor)
   throws Exception {

  ClassWriter cw = new ClassWriter(0);
  FieldVisitor fv;
  MethodVisitor mv;
  AnnotationVisitor av0;

  cw.visit(V1_7, ACC_PUBLIC + ACC_SUPER, dynamicInvokerClassName, null, "java/lang/Object", null);

  {
   mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
   mv.visitCode();
   mv.visitVarInsn(ALOAD, 0);
   mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
   mv.visitInsn(RETURN);
   mv.visitMaxs(1, 1);
   mv.visitEnd();
  }
  {
   mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
   mv.visitCode();
   MethodType mt = MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class,
     MethodType.class);
   Handle bootstrap = new Handle(Opcodes.H_INVOKESTATIC, dynamicLinkageClassName, bootstrapMethodName,
     mt.toMethodDescriptorString());
   int maxStackSize = addMethodParameters(mv);
   mv.visitInvokeDynamicInsn("runCalculation", targetMethodDescriptor, bootstrap);
   mv.visitInsn(RETURN);
   mv.visitMaxs(maxStackSize, 1);
   mv.visitEnd();
  }
  cw.visitEnd();

  return cw.toByteArray();
 }

 protected abstract int addMethodParameters(MethodVisitor mv);

}

public class SimpleDynamicInvokerGenerator extends AbstractDynamicInvokerGenerator {

 @Override
 protected int addMethodParameters(MethodVisitor mv) {
  return 0;
 }

 public static void main(String[] args) throws IOException, Exception {
  String dynamicInvokerClassName = "com/schlimm/bytecode/SimpleDynamicInvoker";
  FileOutputStream fos = new FileOutputStream(new File("target/classes/" + dynamicInvokerClassName + ".class"));
  fos.write(new SimpleDynamicInvokerGenerator().dump(dynamicInvokerClassName, "com/schlimm/bytecode/invokedynamic/linkageclasses/SimpleDynamicLinkageExample", "bootstrapDynamic", "()V"));
 }
 
}

I am using ASM here, an all purpose Java bytecode manipulation and analysis framework, to do the job of creating a correct class file format. In line 30 the visitInvokeDynamicInsn creates the invokedynamic instruction. Generating a class that does an invokedynamic call is only half of the story. You also need some code that links the dynamic call site to the actual target, this is the real purpose of invokedynamic. Here is an example.

public class SimpleDynamicLinkageExample {
 
 private static MethodHandle sayHello;

 private static void sayHello() {
  System.out.println("There we go!");
 }

 public static CallSite bootstrapDynamic(MethodHandles.Lookup caller, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
  MethodHandles.Lookup lookup = MethodHandles.lookup();
  Class thisClass = lookup.lookupClass(); // (who am I?)
  sayHello = lookup.findStatic(thisClass, "sayHello", MethodType.methodType(void.class));
  return new ConstantCallSite(sayHello.asType(type));
 }

}

The bootstrap method in line 9-14 selects the actual target of the dynamic call. In our case the target is the sayHello() method. To learn how the bootstrap method is linked to the invokedynamic instruction we need to dive into the bytecode of SimpleDynamicInvoker that we’ve generated with SimpleDynamicInvokerGenerator.

E:\dev_home\repositories\git\playground\bytecode-playground\target\classes\com\schlimm\bytecode>javap -c -verbose SimpleDynamicInvoker.class

Classfile /E:/dev_home/repositories/git/playground/bytecode-playground/target/classes/com/schlimm/bytecode/SimpleDynamicInvoker.class
  Last modified 30.01.2012; size 512 bytes
  MD5 checksum 401a0604146e2e95f9563e7d9f9d861b
public class com.schlimm.bytecode.SimpleDynamicInvoker
  BootstrapMethods:
    0: #17 invokestatic com/schlimm/bytecode/invokedynamic/linkageclasses/SimpleDynamicLinkageExample.bootstrapDynamic:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
      Method arguments:
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Utf8               com/schlimm/bytecode/SimpleDynamicInvoker
   #2 = Class              #1             //  com/schlimm/bytecode/SimpleDynamicInvoker
   #3 = Utf8               java/lang/Object
   #4 = Class              #3             //  java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = NameAndType        #5:#6          //  "<init>":()V
   #8 = Methodref          #4.#7          //  java/lang/Object."<init>":()V
   #9 = Utf8               main
  #10 = Utf8               ([Ljava/lang/String;)V
  #11 = Utf8               com/schlimm/bytecode/invokedynamic/linkageclasses/SimpleDynamicLinkageExample
  #12 = Class              #11            //  com/schlimm/bytecode/invokedynamic/linkageclasses/SimpleDynamicLinkageExample
  #13 = Utf8               bootstrapDynamic
  #14 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #15 = NameAndType        #13:#14        //  bootstrapDynamic:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #16 = Methodref          #12.#15        //  com/schlimm/bytecode/invokedynamic/linkageclasses/SimpleDynamicLinkageExample.bootstrapDynamic:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #17 = MethodHandle       #6:#16         //  invokestatic com/schlimm/bytecode/invokedynamic/linkageclasses/SimpleDynamicLinkageExample.bootstrapDynamic:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #18 = Utf8               runCalculation
  #19 = NameAndType        #18:#6         //  runCalculation:()V
  #20 = InvokeDynamic      #0:#19         //  #0:runCalculation:()V
  #21 = Utf8               Code
  #22 = Utf8               BootstrapMethods
{
  public com.schlimm.bytecode.SimpleDynamicInvoker();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #8                  // Method java/lang/Object."<init>":()V
         4: return

  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=0, locals=1, args_size=1
         0: invokedynamic #20,  0             // InvokeDynamic #0:runCalculation:()V
         5: return
}

In line 49 you can see the invokedynamic instruction. The logical name of the dynamic method is runCalculation, this is a fictitious name. You can use any name that makes sense, also names like “+” are allowed. The instruction refers to item 20 in the contant pool table (see line 33). This in turn refers to index 0 in the BootstrapMethods attribute (see line 8). There you can see the link to the SimpleDynamicLinkageExample.bootstrapDynamic method that links the invokedynamic instruction to the call target.

Now if you call the SimpleDynamicInvoker using the java launcher, then the invokedynamic call is executed.

The following sequence diagram illustrates what’s happening when the SimpleDynamicInvoker is called using the java launcher.

The first call of runCalculation using invokedynamic issues a call to the bootstrapDynamic method. This method does the dynamic linkage between the calling class (SimpleDynamicInvoker) and the receiving class (SimpleDynamicLinkageExample). The bootstrap method returns a MethodHandle that targets the receiving class. This method handle is cached for repetitive invocations of the runCalculation method.

That’s all in terms of invokedynamic. I have some more sophisticated examples published here in my Git repo. I hope you’ve enjoyed reading this – in times of shortage!

Cheers, Niklas

Reference:

Ilias Tsagklis

Ilias is a software developer turned online entrepreneur. He is co-founder and Executive Editor at Java Code Geeks.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button