Fight the Future

Java言語とJVM、そしてJavaエコシステム全般にまつわること

Groovyでのinvokedynamic

Twitterでのやり取りでGroovyのinvokedynamicの話題が出たので、まず簡単に試してみました。

まず以下のHelloWorld.groovyを作ります。

println 'Hello, world!'

普通にコンパイルする。

$ groovy -v
Groovy Version: 2.4.0 JVM: 1.8.0_65 Vendor: Oracle Corporation OS: Mac OS X
$ groovyc HelloWorld.groovy 

javapする。

$ javap -v HelloWorld
...
  public java.lang.Object run();
    descriptor: ()Ljava/lang/Object;
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=2, args_size=1
         0: invokestatic  #17                 // Method $getCallSiteArray:()[Lorg/codehaus/groovy/runtime/callsite/CallSite;
         3: astore_1
         4: aload_1
         5: ldc           #40                 // int 1
         7: aaload
         8: aload_0
         9: ldc           #42                 // String Hello, world!
        11: invokeinterface #46,  3           // InterfaceMethod org/codehaus/groovy/runtime/callsite/CallSite.callCurrent:(Lgroovy/lang/GroovyObject;Ljava/lang/Object;)Ljava/lang/Object;
        16: areturn
        17: aconst_null
        18: areturn
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      17     0  this   LHelloWorld;
      LineNumberTable:
        line 1: 4

11でGroovy実装のCallSiteなのかな?を使っており、indyではありません。Groovyのcore以外のクラスでindyを使うためには、indyフラグをonにする必要があります。

$ groovyc -indy  HelloWorld.groovy

はいjavap。

$ javap -v HelloWorld
...
  public java.lang.Object run();
    descriptor: ()Ljava/lang/Object;
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: ldc           #44                 // String Hello, world!
         3: invokedynamic #50,  0             // InvokeDynamic #1:invoke:(LHelloWorld;Ljava/lang/String;)Ljava/lang/Object;
         8: areturn
         9: nop
        10: athrow
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   LHelloWorld;
      LineNumberTable:
        line 1: 0
      StackMapTable: number_of_entries = 1
        frame_type = 255 /* full_frame */
          offset_delta = 9
          locals = []
          stack = [ class java/lang/Throwable ]

indyが使われるようになりました。 公式ドキュメントには、indyフラグのon/offでの挙動について記述があります。

For user compiled classes:
indy flag off on
normal jar call site caching N/A
indy jar call site caching invokedynamic
For core Groovy classes:
indy flag off on
normal jar call site caching N/A
indy jar invokedynamic invokedynamic

indyフラグをoffにしていても、Groovyコアのクラスはindy使います、ということです。

コンパイラで見てみる

これらのclassファイルをデコンパイラで見てみましょう。 デコンパイラもいろんなものがありますが、ここではJDを使います。

jd.benow.ca

indyフラグ off

import groovy.lang.Binding;
import groovy.lang.Script;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.callsite.CallSite;

public class HelloWorld
  extends Script
{
  public HelloWorld() {}
  
  public HelloWorld(Binding context)
  {
    super(context);
  }
  
  public static void main(String... args)
  {
    CallSite[] arrayOfCallSite = $getCallSiteArray();
    arrayOfCallSite[0].call(InvokerHelper.class, HelloWorld.class, args);
  }
  
  public Object run()
  {
    CallSite[] arrayOfCallSite = $getCallSiteArray();return arrayOfCallSite[1].callCurrent(this, "Hello, world!");return null;
  }
}

デコンパイルできました。$getCallSiteArray()には触れない(触れられない)。

indyフラグ on

// INTERNAL ERROR //

デコンパイルでエラーになりました。indyを使っていることで、Javaコードに戻せないからですね。