Java 7 invokedynamic の概要

@miyakawa_taku
2012-02-22
JJUG Night Seminar
1



発表者
• 名前: 宮川 拓
• 職業: SI 屋で Hadooper
• オレオレ JVM 言語 Kink を開発中
 – Its’ fun!
2



invokedynamic とは?
• Java 6 までの JVM
  = Java のための仮想マシン
• Java 7 の JVM
  = Java + Java 以外の言語のための仮想マシン
• invokedynamic は Java 以外の言語のために
  追加された新しいメソッド呼び出し命令
3



論点
• Java 6 までの呼び出し命令
  – JVM 命令は Java のために作られていた
• Java 以外の言語
  – 既存の命令セットでは Java 以外の言語処理系が
    効率的に実装できない
• invokedynamic
  – 新しい命令で Java 以外の言語も効率的になる
4



論点
• Java 6 までの呼び出し命令
• Java 以外の言語
• invokedynamic
5



Java 6 までの呼び出し命令
• 4 種類
• Java のメソッド呼び出しはこれで全部 OK

    invokestatic static メソッドを呼び出す
  invokespecial コンストラクタ、 private メソッド等を呼び出す
   invokevirtual クラスに属するメソッドを呼び出す (非 private)
invokeinterface インタフェースに属するメソッドを呼び出す
6



invokestatic
• static メソッドの呼び出し → invokestatic

             StringUtils.rjust("VOXXX", 10, '.');
                                    javac
   ldc "VOXXX"      // "VOXXX” をスタックに積む
   bipush 10       // 10 をスタックに積む
   bipush 46       // '.' = 46 をスタックに積む
   invokestatic StringUtils#rjust(String,int,char):String
7



invokestatic
• invokestatic 命令は与えられたメソッドをその
  まま呼び出す

                         class StringUtils
                 bang!   static String rjust(String,int,char)
  invokestatic              iload_1
                            aload_0
                            ...
8



invokespecial
• private なインスタンスメソッド、コンストラクタ、
  super メソッドの呼び出し → invokespecial

              this.checkIndex(index);
                            javac
    aload_0 // this (レシーバ) をスタックに積む
    iload_1 // index をスタックに積む
    invokespecial MyList#checkIndex(int):void
9



invokespecial
• invokespecial 命令は与えられたメソッドをその
  まま呼び出す (invokestatic とほぼ同じ)

                          class MyList
                  bang!   private void checkIndex(int)
  invokespecial            iconst_0
                           iload_1
                           ...
10



invokevirtual
• とある GUI ツールキットのクラス階層
                   Widget
                   Rect getRect()

                    Button
                    void push()

    CheckBox                      OKButton
    void push()                   void push()
    boolean isPushed()
11



invokevirtual
• レシーバの型がクラスであるインスタンスメ
  ソッド呼び出し (除 private) → invokevirtual

                Button button;
                button.push();
                           javac
        aload_0
        invokevirtual Button#push():void
12



invokevirtual
• invokevirtual 命令はレシーバのクラスが持つ
  メソッドのルックアップテーブル (vtable) を見
  て呼び出すメソッドを決める
                Button    CheckBox   OKButton
                getRect   getRect    getRect
invokevirtual   push      push       push
 Button クラスの              isPushed
 2 番目のメソッド
 を呼ぶ!
                 メソッドへのポインタが
                   格納されている
13



invokeinterface
• 再び GUI ツールキットのクラス階層
    intf Movable       intf Resizable
    void move(Point)   void resize(Size)



    class Icon         class Frame
    void move(Point)   void resize(Size)
                       void move(Point)
14



invokeinterface
• レシーバの型がインタフェースであるインスタ
  ンスメソッド呼び出し → invokeinterface
          Movable window;
          Point point;
          window.move(new Point(0, 0));
                           javac
     aload_0
     aload_1
     invokeinterface Movable#move(Point):void
15



invokeinterface
• invokeinterface 命令はレシーバのクラスがイ
  ンタフェース毎に持つルックアップテーブル
  (itable) を見て呼び出すメソッドを決める
                   class Icon       class Frame

                   Movable      Movable    Resizable
invokeinterface    move         move       resize
Movable インタフェースの
1 番目のメソッドを呼ぶ!       メソッドへのポインタが
                      格納されている
16



Java 6 までの呼び出し命令 (rep)
• 4 つの呼び出し命令で Java のメソッド呼び出
  しをカバー
• Java に特化した仕組み
 – メソッドが再定義されることはない
 – 単一継承
 – 名前と型の組み合わせによるメソッドの特定
 – レシーバのクラスによる単一ディスパッチ
17



論点
• Java 6 までの呼び出し命令
• Java 以外の言語
• invokedynamic
18



古い革袋と新しい酒

                Scheme
              JS       Python
            Groovy Ruby
                        Scala
   Java           Java
   JVM            JVM

  むかし             最近
19



古い革袋と新しい酒
• JVM は Java のために作られたので、それ以外
  の言語を動かすのには工夫が必要
• 例: Ruby ではメソッドが再定義できるので、既
  存の呼び出し命令で直接呼び出せない
• 他にも
 – method-missing
 – 多重継承
 – mix-in
20

Java 6 までは呼び出すために
処理系が間に挟まる必要があった
    array.join
バイトコード
  生成
              invoke          invoke
    CAFE 0000 virtual         virtual def join
    3939 5151          処理系             ...
    ......                             ...
                      検索
                 size   Func@42
                 join   Func@123
21



本当はこうしたい!
    array.join
バイトコード
  生成
    CAFE 0000            bang!         def join
    3939 5151                           ...
    ......                              ...


                 余計な処理がなく、JIT コンパイラに
                  よる最適化が掛けやすくなる
22



新しい呼び出し命令が欲しい
• Java 以外の言語のメソッドを JVM の命令で直
  接呼び出したい
• しかも
 – 実行される処理を独自のルールで検索したい
 – メソッドを実行時に繋ぎ変えたい
 – JIT でカリカリに最適化してほしい
23



論点
• Java 6 までの呼び出し命令
• Java 以外の言語
• invokedynamic
24



invokedynamic
• invokedynamic による呼び出しが実現すること
 – 呼び出す処理の選択をプログラムによって制御で
   きる
 – 呼び出す処理を実行時に繋ぎ変えられる
 – カリカリに最適化して実行する
25



invokedynamic の基本コンセプト
• やりたいこと
 – 命令ごとに関数ポインタを登録、これが指し示す
   先の処理を呼び出す
 – 別の関数ポインタを登録しなおすことも可能
   invokedynamic

      関数           bang!   対象の
     ポインタ                  処理

  あとから
 貼り替えられる
26



invokedynamic の道具立て
• 仔細に道具立てを見ると下図の通り

        初回実行時に           invokedynamic
        呼び出し
bootstrap   <<create>>
                            CallSite
 メソッド


 任意の         MH の           Method     bang!   対象の
 処理          再登録            Handle             処理
27



MethodHandle
• まずは MethodHandle

         初回実行時に           invokedynamic
         呼び出し
 bootstrap   <<create>>
                             CallSite
  メソッド


  任意の         MH の           Method     bang!   対象の
  処理          再登録            Handle             処理
28



MethodHandle
• MethodHandle は型付き関数ポインタ
• 決まった型・数の引数をスタックから取り、結
  果をスタックに置く処理を指し示す
プリミティブな MethodHandle の作成
Lookup#findVirtual     • インスタンスメソッドを呼び出す
                         MH
Lookup#findConstructor • インスタンスを生成する MH
Lookup#findGetter      • フィールドの値を返す MH
MethodHandles#constant • 定数値を返す MH
29



MethodHandle
• MethodHandle は合成したり、引数の順序を
  入れ替えたり、部分適用したりして新しい
  MethodHandle を生成できる
複合的な MethodHandle の作成
MethodHandles  • (if test then target else fallback) を行う
#guardWithTest   MH
MethodHandles        • 本処理の戻り値に後処理を加える MH
#filterReturnValue
MethodHandle         • 先頭の引数の値を固定した MH
#bindTo
30



CallSite
• ついで CallSite

         初回実行時に           invokedynamic
         呼び出し
 bootstrap   <<create>>
                             CallSite
  メソッド


  任意の         MH の           Method     bang!   対象の
  処理          再登録            Handle             処理
31



CallSite
• 1 つの invokedynamic 命令に紐付いて「呼び
  出し元」を表す
• MethodHandle の参照を保持する
CallSite の具象クラス
ConstantCallSite •   MH が書き換えられない
                 •   private final MethodHandle mh
MutableCallSite  •   MH が書き換えられる
                 •   private MethodHandle mh
VolatileCallSite   • MH が書き換えられる
                   • private volatile MethodHandle mh
32



bootstrap メソッド
• 最後に bootstrap メソッド

        初回実行時に            invokedynamic
        呼び出し
 bootstrap   <<create>>
                             CallSite
  メソッド


  任意の         MH の           Method     bang!   対象の
  処理          再登録            Handle             処理
33



bootstrap メソッド
• 各 invokedynamic 命令は bootstrap という
  static メソッドへの参照を持っている
• invokedynamic 命令が最初に実行される時に
  bootstrap メソッドが呼ばれて
  – 命令に CallSite オブジェクトを紐付ける
  – MethodHandle の初期値を CallSite に紐付ける
34



bootstrap メソッド
• 例: 戻り値の型が int の場合、強制的に値を
  42 にする
 static CallSite bsm(Lookup lu, String name, MethodType mt)
 throws Exception {
   MethodType vmt = mt.dropParameterTypes(0, 1);
   MethodHandle vmh = lu.findVirtual(mt.parameterType(0), name, vmt);
   if (vmt.returnType() == int.class)
      return new ConstantCallSite(filterReturnValue(vmh,
        dropArguments(constant(int.class, 42), 0, int.class)));
   else
      return new ConstantCallSite(vmh);
 }
35



bootstrap メソッド
• 例: 「メソッド名」を文字列として戻す
  – indy での「メソッド名」は bootstrap に渡す引数に
    過ぎない

 static CallSite bsm(Lookup lu, String name, MethodType mt) {
   return new ConstantCallSite(dropArguments(
      constant(String.class, name), 0, mt.parameterType(0)));
 }



              MethodType が引数なし
             戻り値 String でないとエラー
36



invokedynamic (rep)
• bootstrap メソッドにより、命令に紐付く処理を
  言語処理系独自のやり方で選択できる
• CallSite に新しい MethodHandle を登録するこ
  とにより、命令に紐付く処理を実行時に変更
  できる
• これらの仕組みを JVM のレベルでサポートす
  るため、賢く使うと効率的な言語処理系が実
  装できる
37



蛇足
• MethodHandle は処理をオブジェクトとして
  様々に操作できるから面白い!
• たとえば MethodHandle ベースの AOP フレー
  ムワークが作れるかも
38



参考
• Da Vinci Machine Project (mlvm)
   – http://openjdk.java.net/projects/mlvm/
• JSR 292 Cookbook
   – http://cr.openjdk.java.net/~jrose/pres/200906-Cookbook.htm
• John Rose’ weblog at Oracle: dynamic invocation in the VM
   – https://blogs.oracle.com/jrose/entry/dynamic_invocation_in_the_vm
• Optimizing invokedynamic
   – http://dl.acm.org/citation.cfm?id=1852763
• HotSpot Internals for OpenJDK: CallingSequences
   – https://wikis.oracle.com/display/HotSpotInternals/CallingSequences

Java 7 invokedynamic の概要

  • 1.
    Java 7 invokedynamicの概要 @miyakawa_taku 2012-02-22 JJUG Night Seminar
  • 2.
    1 発表者 • 名前: 宮川拓 • 職業: SI 屋で Hadooper • オレオレ JVM 言語 Kink を開発中 – Its’ fun!
  • 3.
    2 invokedynamic とは? • Java6 までの JVM = Java のための仮想マシン • Java 7 の JVM = Java + Java 以外の言語のための仮想マシン • invokedynamic は Java 以外の言語のために 追加された新しいメソッド呼び出し命令
  • 4.
    3 論点 • Java 6までの呼び出し命令 – JVM 命令は Java のために作られていた • Java 以外の言語 – 既存の命令セットでは Java 以外の言語処理系が 効率的に実装できない • invokedynamic – 新しい命令で Java 以外の言語も効率的になる
  • 5.
    4 論点 • Java 6までの呼び出し命令 • Java 以外の言語 • invokedynamic
  • 6.
    5 Java 6 までの呼び出し命令 •4 種類 • Java のメソッド呼び出しはこれで全部 OK invokestatic static メソッドを呼び出す invokespecial コンストラクタ、 private メソッド等を呼び出す invokevirtual クラスに属するメソッドを呼び出す (非 private) invokeinterface インタフェースに属するメソッドを呼び出す
  • 7.
    6 invokestatic • static メソッドの呼び出し→ invokestatic StringUtils.rjust("VOXXX", 10, '.'); javac ldc "VOXXX" // "VOXXX” をスタックに積む bipush 10 // 10 をスタックに積む bipush 46 // '.' = 46 をスタックに積む invokestatic StringUtils#rjust(String,int,char):String
  • 8.
    7 invokestatic • invokestatic 命令は与えられたメソッドをその まま呼び出す class StringUtils bang! static String rjust(String,int,char) invokestatic iload_1 aload_0 ...
  • 9.
    8 invokespecial • private なインスタンスメソッド、コンストラクタ、 super メソッドの呼び出し → invokespecial this.checkIndex(index); javac aload_0 // this (レシーバ) をスタックに積む iload_1 // index をスタックに積む invokespecial MyList#checkIndex(int):void
  • 10.
    9 invokespecial • invokespecial 命令は与えられたメソッドをその まま呼び出す (invokestatic とほぼ同じ) class MyList bang! private void checkIndex(int) invokespecial iconst_0 iload_1 ...
  • 11.
    10 invokevirtual • とある GUIツールキットのクラス階層 Widget Rect getRect() Button void push() CheckBox OKButton void push() void push() boolean isPushed()
  • 12.
    11 invokevirtual • レシーバの型がクラスであるインスタンスメ ソッド呼び出し (除 private) → invokevirtual Button button; button.push(); javac aload_0 invokevirtual Button#push():void
  • 13.
    12 invokevirtual • invokevirtual 命令はレシーバのクラスが持つ メソッドのルックアップテーブル (vtable) を見 て呼び出すメソッドを決める Button CheckBox OKButton getRect getRect getRect invokevirtual push push push Button クラスの isPushed 2 番目のメソッド を呼ぶ! メソッドへのポインタが 格納されている
  • 14.
    13 invokeinterface • 再び GUIツールキットのクラス階層 intf Movable intf Resizable void move(Point) void resize(Size) class Icon class Frame void move(Point) void resize(Size) void move(Point)
  • 15.
    14 invokeinterface • レシーバの型がインタフェースであるインスタ ンスメソッド呼び出し → invokeinterface Movable window; Point point; window.move(new Point(0, 0)); javac aload_0 aload_1 invokeinterface Movable#move(Point):void
  • 16.
    15 invokeinterface • invokeinterface 命令はレシーバのクラスがイ ンタフェース毎に持つルックアップテーブル (itable) を見て呼び出すメソッドを決める class Icon class Frame Movable Movable Resizable invokeinterface move move resize Movable インタフェースの 1 番目のメソッドを呼ぶ! メソッドへのポインタが 格納されている
  • 17.
    16 Java 6 までの呼び出し命令(rep) • 4 つの呼び出し命令で Java のメソッド呼び出 しをカバー • Java に特化した仕組み – メソッドが再定義されることはない – 単一継承 – 名前と型の組み合わせによるメソッドの特定 – レシーバのクラスによる単一ディスパッチ
  • 18.
    17 論点 • Java 6までの呼び出し命令 • Java 以外の言語 • invokedynamic
  • 19.
    18 古い革袋と新しい酒 Scheme JS Python Groovy Ruby Scala Java Java JVM JVM むかし 最近
  • 20.
    19 古い革袋と新しい酒 • JVM はJava のために作られたので、それ以外 の言語を動かすのには工夫が必要 • 例: Ruby ではメソッドが再定義できるので、既 存の呼び出し命令で直接呼び出せない • 他にも – method-missing – 多重継承 – mix-in
  • 21.
    20 Java 6 までは呼び出すために 処理系が間に挟まる必要があった array.join バイトコード 生成 invoke invoke CAFE 0000 virtual virtual def join 3939 5151 処理系 ... ...... ... 検索 size Func@42 join Func@123
  • 22.
    21 本当はこうしたい! array.join バイトコード 生成 CAFE 0000 bang! def join 3939 5151 ... ...... ... 余計な処理がなく、JIT コンパイラに よる最適化が掛けやすくなる
  • 23.
    22 新しい呼び出し命令が欲しい • Java 以外の言語のメソッドをJVM の命令で直 接呼び出したい • しかも – 実行される処理を独自のルールで検索したい – メソッドを実行時に繋ぎ変えたい – JIT でカリカリに最適化してほしい
  • 24.
    23 論点 • Java 6までの呼び出し命令 • Java 以外の言語 • invokedynamic
  • 25.
    24 invokedynamic • invokedynamic による呼び出しが実現すること – 呼び出す処理の選択をプログラムによって制御で きる – 呼び出す処理を実行時に繋ぎ変えられる – カリカリに最適化して実行する
  • 26.
    25 invokedynamic の基本コンセプト • やりたいこと – 命令ごとに関数ポインタを登録、これが指し示す 先の処理を呼び出す – 別の関数ポインタを登録しなおすことも可能 invokedynamic 関数 bang! 対象の ポインタ 処理 あとから 貼り替えられる
  • 27.
    26 invokedynamic の道具立て • 仔細に道具立てを見ると下図の通り 初回実行時に invokedynamic 呼び出し bootstrap <<create>> CallSite メソッド 任意の MH の Method bang! 対象の 処理 再登録 Handle 処理
  • 28.
    27 MethodHandle • まずは MethodHandle 初回実行時に invokedynamic 呼び出し bootstrap <<create>> CallSite メソッド 任意の MH の Method bang! 対象の 処理 再登録 Handle 処理
  • 29.
    28 MethodHandle • MethodHandle は型付き関数ポインタ •決まった型・数の引数をスタックから取り、結 果をスタックに置く処理を指し示す プリミティブな MethodHandle の作成 Lookup#findVirtual • インスタンスメソッドを呼び出す MH Lookup#findConstructor • インスタンスを生成する MH Lookup#findGetter • フィールドの値を返す MH MethodHandles#constant • 定数値を返す MH
  • 30.
    29 MethodHandle • MethodHandle は合成したり、引数の順序を 入れ替えたり、部分適用したりして新しい MethodHandle を生成できる 複合的な MethodHandle の作成 MethodHandles • (if test then target else fallback) を行う #guardWithTest MH MethodHandles • 本処理の戻り値に後処理を加える MH #filterReturnValue MethodHandle • 先頭の引数の値を固定した MH #bindTo
  • 31.
    30 CallSite • ついで CallSite 初回実行時に invokedynamic 呼び出し bootstrap <<create>> CallSite メソッド 任意の MH の Method bang! 対象の 処理 再登録 Handle 処理
  • 32.
    31 CallSite • 1 つのinvokedynamic 命令に紐付いて「呼び 出し元」を表す • MethodHandle の参照を保持する CallSite の具象クラス ConstantCallSite • MH が書き換えられない • private final MethodHandle mh MutableCallSite • MH が書き換えられる • private MethodHandle mh VolatileCallSite • MH が書き換えられる • private volatile MethodHandle mh
  • 33.
    32 bootstrap メソッド • 最後にbootstrap メソッド 初回実行時に invokedynamic 呼び出し bootstrap <<create>> CallSite メソッド 任意の MH の Method bang! 対象の 処理 再登録 Handle 処理
  • 34.
    33 bootstrap メソッド • 各invokedynamic 命令は bootstrap という static メソッドへの参照を持っている • invokedynamic 命令が最初に実行される時に bootstrap メソッドが呼ばれて – 命令に CallSite オブジェクトを紐付ける – MethodHandle の初期値を CallSite に紐付ける
  • 35.
    34 bootstrap メソッド • 例:戻り値の型が int の場合、強制的に値を 42 にする static CallSite bsm(Lookup lu, String name, MethodType mt) throws Exception { MethodType vmt = mt.dropParameterTypes(0, 1); MethodHandle vmh = lu.findVirtual(mt.parameterType(0), name, vmt); if (vmt.returnType() == int.class) return new ConstantCallSite(filterReturnValue(vmh, dropArguments(constant(int.class, 42), 0, int.class))); else return new ConstantCallSite(vmh); }
  • 36.
    35 bootstrap メソッド • 例:「メソッド名」を文字列として戻す – indy での「メソッド名」は bootstrap に渡す引数に 過ぎない static CallSite bsm(Lookup lu, String name, MethodType mt) { return new ConstantCallSite(dropArguments( constant(String.class, name), 0, mt.parameterType(0))); } MethodType が引数なし 戻り値 String でないとエラー
  • 37.
    36 invokedynamic (rep) • bootstrapメソッドにより、命令に紐付く処理を 言語処理系独自のやり方で選択できる • CallSite に新しい MethodHandle を登録するこ とにより、命令に紐付く処理を実行時に変更 できる • これらの仕組みを JVM のレベルでサポートす るため、賢く使うと効率的な言語処理系が実 装できる
  • 38.
    37 蛇足 • MethodHandle は処理をオブジェクトとして 様々に操作できるから面白い! • たとえば MethodHandle ベースの AOP フレー ムワークが作れるかも
  • 39.
    38 参考 • Da VinciMachine Project (mlvm) – http://openjdk.java.net/projects/mlvm/ • JSR 292 Cookbook – http://cr.openjdk.java.net/~jrose/pres/200906-Cookbook.htm • John Rose’ weblog at Oracle: dynamic invocation in the VM – https://blogs.oracle.com/jrose/entry/dynamic_invocation_in_the_vm • Optimizing invokedynamic – http://dl.acm.org/citation.cfm?id=1852763 • HotSpot Internals for OpenJDK: CallingSequences – https://wikis.oracle.com/display/HotSpotInternals/CallingSequences