invokedynamic: Is It What We Really Need?

John Rose wrote interesting post regarding dynamic invocation in the VM. Here are my thoughts as implementor of Groovy language.

DECLAIMER: It is not official opinion of Groovy Development Team or G2One Inc. but only my personal opinion.

First of all I think John and JSR 292 team finally start to do very good job. After long period of uncertainty we have a lot of communication last months about what’s going on and ideas under discussion. It is really helpful.

Finally we have very clear explanation of method handles idea, which sounds very promising. To make long story short it is about avoiding expensive calls to Reflection API, which is one of the biggest problems each dynamic runtime meet. And now we have more or less clear picture (or suggestion) for invokedynamic.

Recent significant progress in JRuby and Groovy runtime performance proved clearly that dynamic languages has a lot of space for improvements in terms of performance. It is also more clear now what kind of support is required from JVM for dynamic language runtime.

In fact, the main challenge for implementor of dynamic language sounds pretty trivial: you have call to the method and in general can assume nothing about type of receiver and types of arguments. So execution of a call consist <i>logically</i> of two important steps:

  • method selection
  • method invocation

IMPORTANT NOTE: Current Groovy MOP doesn’t separate these steps, which also leads to some difficulties. Of course, implicitly (logically) both steps are presented. For Groovy MOP 2.0 we consider possibility to separate them explicitly.

Method invocation in Groovy is expensive. We have to use Reflection API, which does a lot of checks, requires allocating of array for result, boxing/unboxing of arguments and return value and several nested calls before we come to execution of real method. As I said before there is some hope that methods handle will help here. What is especially beautiful in method handles is the fact that they don’t require new bytecode but can be handled directly by JVM.

Method selection in Groovy is even more expensive. Following diagram gives very simplified overview of what’s going on. Let me repeat this: it is simplified overview and real process even more complicated (categories, ExpandoMetaClass etc.)

Fortunately there is solution for the method selection problem: if we store result of previous method selection there is very good chance that next time our choice will be the same and check of this fact is much cheaper compare to real selection process. This technique is implemented in Groovy 1.6 and proved to be very efficient (of course some memory overhead is involved)

Here is pseudo-code of API used by Groovy 1.6

public abstract class CallSite {
    // index of call site
    protected final int index;
    
    // name of method
    public final String name;
    
    // array of all call sites in compiled class
    protected final CallSiteArray array;
   
   // calls to this methods appear in bytecode
   public Object call (Object receiver, Object [] args) {
      retutn acceptCall(receiver,args).invoke(receiver,args);
   }
  
   // check if receiver, arguments, categories in use, etc. fits to memorized choice
   // if so, we can continue to use same call site
   // if not, new call site created to replace current one
   protected CallSite acceptCall (Object receiver, Object [] args) {
      if (checkCall(receiver,args))
         return this;
      else
         return createCallSite(this,receiver,args);
   }  

   // check if receiver, arguments, categories in use, etc. fits to memorized choice
   // depends on type of call site
   protected abstract boolean checkCall (Object receiver, Object [] args);

   // real invocation
   // depends on type of call site
   protected abstract Object invoke (Object receiver, Object [] args);

   // creates new call site, which will replace existing one
   protected final CallSite createCallSite(CallSite base, Object receiver, Object [] args) {
      ....
   }
}

In bytecode receiver.method (arg1, arg2) becomes

callSiteArray[callSiteIndex].call (receiver, new Object[] {arg1, arg2} );

callSiteArray is synthetic local variable filled in the beginning of each method from static soft referenced field. Initially array is filled by dummy call sites, which needed to keep name of target method and index of call site, because we don’t want to send two additional parameters each time and we also don’t want to do null check for call site on each invocation.

So what happen when we visit call site first time is creation of real call site and invocation. On second visit we check if it still to be valid and either recreate or use it.

Truely speaking, API above is not real API. There are special paths reflecting the fact that in Groovy there is several different types of method call but it is out of scope of this note and I hope the idea is clear. Another thing, which makes API much more complex is special paths for binary operations and in the future for calls with small number of parameters, which allow to avoid creating of argument arrays.

So how all that is related to invokedynamic? Very simply. Bootstrap method will call our createCallSite. Target handle will be CallSite.call doing check and either invoking another method handle (really selected method) or setting new target. And voila!

The only thing, which really worries me is do we need special bytecode or Java API (of course specially recognized and optimized by JVM) is enough.

The real beauty of API is that you can use it (backported versions) for old JVMs. Even now a lot of people stay with 1.4 and I am not sure how many already switched to 1.6. For us, as language developers, very important to support old platforms. For sure, to support 1.5. And I would prefer not to have two versions of compiler for platform supporting invokedynamic and for one, which doesn’t.

The only really good side of invokedynamic compare to fixed API is ability to make calls with absolutely any signature. I don’t know how important is that for other languages, for Groovy I think it will be enough to have rich enough fixed API for small number of arguments and Reflection-like general API.

So if I was member of EG, which I am not, I would prefer not to introduce new bytecode.

Dixi :)

About these ads

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: