invokedynamic調査メモ

さんざん色々なところで紹介されているので今更感はありますが、Java SE 7で導入されたinvokedynamicの(自分向け)メモ。

# invokedynamicとは
Java Virtual Machine(JVM)の命令で、これを使うと、メソッド呼び出し元と呼び出し先のリンク処理を、アプリケーション側でカスタマイズできます。(invokedynamic以外のメソッド呼び出し用JVM命令(invokeinterface、invokespecial、invokestatic、invokevirtual)の場合は、リンク処理はJVM側で定義されていた)

##従来の命令の場合
例えば、以下のJavaプログラムは、javacを使うと以下のバイトコードにコンパイルされます。

```java:A.java
class A {
  public static void main(String[] args) {
    String name = "Taro";
    A a = new A();
    a.say(name);
  }

  public void say(String name) {
    System.out.println("Hello, " + name);
  }
}
```

```
$ javac A.java 

$ java A
Hello, Taro

$ javap -p -v -c A

  --- snip ---

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #2                  // String Taro
         2: astore_1
         3: new           #3                  // class A
         6: dup
         7: invokespecial #4                  // Method "<init>":()V
        10: astore_2
        11: aload_2
        12: aload_1
        13: invokevirtual #5                  // Method say:(Ljava/lang/String;)V
        16: return
```

- 10番目: astore_2で、スタック上のAオブジェクトをポップして、ローカル変数2に代入。
- 11番目: aload_2で、ローカル変数2のAオブジェクトをスタックにプッシュ。
- 12番目: aload_1で、ローカル変数1の"Taro"オブジェクトをスタックにプッシュ。
- 13番目: invokevirtualで、スタック上のAオブジェクトに対して、スタック上の"Taro"オブジェクトを引数に渡し、say()メソッドを呼び出す。

という流れになります。

invokevirtualは、インスタンスメソッド実行時に使われるJVM命令です。この時の処理の流れは以下の通りです。

```
[呼び出し元]             [呼び出し先]
A.main(String[])  --->  A#say(String)
```

##invokedynamicの場合

次に、以下のプログラムを考えます。このプログラムをコンパイルすると、invokedynamicを使ったバイトコードが生成されます。

(実はjava.util.functionを使うのはあまりいい例ではありませんが、これくらいしかjavacでinvokedynamicが生成されるの知らなかったため)

```java:B.java
import java.util.function.*;

class B {
  public static void main(String[] args) {
    String name = "Taro";
    Consumer f = a -> System.out.println("Hello, " + a);
    f.accept("Taro");
  }
}
```

```
$ javac B.java 
注意:B.javaの操作は、未チェックまたは安全ではありません。
注意:詳細は、-Xlint:uncheckedオプションを指定して再コンパイルしてください。

$ java B
Hello, Taro

$ javap -p -v -c B

  --- snip ---

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #2                  // String Taro
         2: astore_1
         3: invokedynamic #3,  0              // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;
         8: astore_2
         9: aload_2
        10: aload_1
        11: invokeinterface #4,  2            // InterfaceMethod java/util/function/Consumer.accept:(Ljava/lang/Object;)V
        16: return
```


- 3番目のinvokedynamicで、謎メソッド(1)を呼び出す。
- 8番目のastore_2で、invokedynamicの結果の謎オブジェクト(2)をローカル変数2に代入。
- 9番目のaload_2で、謎オブジェクト(2)をスタックにプッシュ
- 10番目のaload_1で、"Taro"オブジェクトをスタックにプッシュ
- 11番目のinvokeinterfaceで、謎オブジェクト(2)に対して、"Taro"オブジェクトを引数に渡し、accept()メソッドを呼び出す。

という流れになります。
ここで、謎メソッド(1)がbootstrap methodと呼ばれる特殊なメソッドです。java.util.functionを使うとJavaの標準APIのものが使われますが、アプリで指定することもできます。bootstrap methodはjavapで解析すると以下のようにでます。

```
$ javap -p -v -c B

  --- snip ---

BootstrapMethods:
  0: #28 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #29 (Ljava/lang/Object;)V
      #30 invokestatic B.lambda$main$0:(Ljava/lang/Object;)V
      #29 (Ljava/lang/Object;)V
```

この場合は、bootstrap methodとして、java.lang.invoke.LambdaMetafactory.metafactory()が使われるようです。
bootstrap methodは、引数に呼び出し先メソッドの情報が与えられ、呼び出し先メソッドを決定して返します(これを「リンク処理」と言っている)。

処理の流れを簡単に図示すると以下のようになります。JVMは、invokedynamicが実行されると、bootstrap methodを呼び出します。bootstrap methodは、call siteを返します。call siteは、メソッド呼び出し先を指し示すもので、今回の場合はB.lambda$main$0(java.lang.Object)を指し示すcall siteが返ります。JVMは、返ったcall siteを見て、メソッド呼び出し処理を行います。同じinvokedynamicを再び実行する際は、bootstrap methodは呼び出さず、前回と同じメソッドの呼び出し処理を行います(リンクされたメソッドを後から変更する方法もあるみたい)。

```
【初回のinvokedynamic実行】
[呼び出し元]             [呼び出し先]
B.main() --+  +-------> B.lambda$main$0(java.lang.Object) (ラムダ式の処理を実装したメソッド)
           |  |
           V  |
           LambdaMetafactory.metafactory()
           [bootstrap method]


【二回目以降のinvokedynamic実行】
[呼び出し元]             [呼び出し先]
B.main() -------------> B.lambda$main$0(java.lang.Object) (ラムダ式の処理を実装したメソッド)
```

# invokedynamicを使ってみる

完全なソースコードは[こちら](https://github.com/hashiwa000/TestInvokeDynamic/tree/v1.0)。バイトコード生成には[ASM](http://asm.ow2.org/)を使っています(ズルして、JDK内部に取り込まれば非公開のASMを使ってます)。

以下はinvokedynamicを使ったバイトコードを生成する処理です。このバイトコードは、callInvokeDynamic(Object, Object)メソッドを含んでいます。callInvokeDynamic()メソッドの中では、引数のオブジェクト２つをスタックにプッシュし、invokedynamicを実行して、返った結果をreturnします。

```java:MyClassFactory.java
  private byte[] createTestInvokeDynamic() {
    String className = "TestInvokeDynamic";
    String methodName = "callInvokeDynamic";
    ClassWriter cw = new ClassWriter(COMPUTE_MAXS);
    MethodVisitor mv;
    MethodType bootstrapMethodType = MethodType.methodType(
            CallSite.class,
            MethodHandles.Lookup.class,
            String.class,
            MethodType.class);
    Handle bootstrapMethodHandle = new Handle(
            H_INVOKESTATIC,
            MyMethodFactory.class.getName().replace('.', '/'),
            "bootstrap",
            bootstrapMethodType.toMethodDescriptorString());


    cw.visit(V1_8, ACC_PUBLIC, className, null, "java/lang/Object", null);

    mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, methodName, "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", null, null);
    mv.visitCode();
    mv.visitVarInsn(ALOAD, 0);
    mv.visitVarInsn(ALOAD, 1);
    mv.visitInvokeDynamicInsn(
            "adder",
            "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
            bootstrapMethodHandle
    );
    mv.visitInsn(ARETURN);
    mv.visitMaxs(2,1);
    mv.visitEnd();

    cw.visitEnd();

    return cw.toByteArray();
  }
```

初回のinvokedynamic実行時は、bootstrap methodが呼ばれます。今回bootstrap methodとして登録したのは以下です。bootstrap methodの中では、呼び出し先メソッド(IntegerOps.adder(Integer, Integer))を探してきて、そのメソッドのハンドルを持つcall siteを返しています。

```java:MyMethodFactory.java
  public static CallSite bootstrap(MethodHandles.Lookup callerClass,
                               String dynMethodName,
                               MethodType dynMethodType) throws Throwable
  {
    System.out.println("In bootstrap method: " + callerClass + ", " + dynMethodName + ", " + dynMethodType);
    MethodHandle mh;
    if (dynMethodType.parameterCount() == 2) {
      mh = callerClass.findStatic(
              IntegerOps.class,
              "adder",
              MethodType.methodType(Integer.class, Integer.class, Integer.class));
    } else {
      mh = callerClass.findStatic(
              IntegerOps.class,
              "adder",
              MethodType.methodType(Integer.class, Integer.class, Integer.class, Integer.class));
    }

    if (!dynMethodType.equals(mh.type())) {
      mh = mh.asType(dynMethodType);
    }

    return new ConstantCallSite(mh);
  }
```

最終的に、JVMは、IntegerOps.addr(Integer, Integer)を呼び出し先メソッドとして認識し、リンクして、呼び出します。

```java:IntegerOps.java
  public static Integer adder(Integer x, Integer y) {
    return x + y;
  }
  public static Integer adder(Integer x, Integer y, Integer z) {
    return x + y + z;
  }
```

これにより、メソッド呼び出し先の決定をアプリケーションで制御できるようになります。しかも、呼び出し先メソッドはJavaに限らないらしい。さらに、メソッド呼び出し先の「リンク」を(PythonやRubyのように)実行時に行うため、動的な型付け言語へも対応しやすくなるという利点があります。こちらの詳細は[動的型付け言語のコンパイルの課題](https://docs.oracle.com/javase/jp/8/docs/technotes/guides/vm/multiple-language-support.html#challenges)により詳しく載っています。

以上

# 参考文献
- https://docs.oracle.com/javase/jp/8/docs/technotes/guides/vm/multiple-language-support.html#challenges
- http://blog.kengo-toda.jp/entry/20101103/1288779426