Вопрос про invokedynamic
От: elw00d Россия http://elwood.su
Дата: 27.05.15 09:15
Оценка:
Разбираюсь с сабжем, и непонятно вот что. Везде пишут, что invokedynamic — это изобретение в первую очередь для поддержки динамических вызовов. То есть например мы — разработчики компилятора/рантайма какого-то динамического языка. И у нас есть код навроде

var a = getSomeA(); // Тип переменной a заранее не известен
var b = getSomeB(); // Тип переменной b тоже заранее не известен
a.foo(123); // Дёргаем метод a.foo(int), если таковой имеется у объекта a
a.foo(b); // Дёргаем метод a.foo(type(b)), если таковой имеется


То есть при компиляции мы не знаем типов ни вызывающего объекта, ни аргументов. Как же в таком случае нам поможет инструкция invokedynamic, если первым её параметром в байт-коде является ссылка на константу InvokeDynamic в пуле констант. А константа InvokeDynamic в свою очередь ссылается на bootstrap method и на константу NameAndType.

В случае статически типизированных лямбд всё понятно:

Constant pool:
#5 = InvokeDynamic      #0:#41         // #0:apply:()Ljava/util/function/Function;
#41 = NameAndType        #59:#60        // apply:()Ljava/util/function/Function;
#59 = Utf8               apply
#60 = Utf8               ()Ljava/util/function/Function;

BootstrapMethods:
  0: #37 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:
      #38 (Ljava/lang/Object;)Ljava/lang/Object;
      #39 invokestatic org/jcoro/Test.lambda$foo$2:(Ljava/lang/Integer;)Ljava/lang/Boolean;
      #40 (Ljava/lang/Integer;)Ljava/lang/Boolean;

public static void foo();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=0
         0: new           #2                  // class java/util/ArrayList
         3: dup
         4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
         7: astore_0
         8: aload_0
         9: invokeinterface #4,  1            // InterfaceMethod java/util/List.stream:()Ljava/util/stream/Stream;
        14: invokedynamic #5,  0              // InvokeDynamic #0:apply:()Ljava/util/function/Function;
        19: invokeinterface #6,  2            // InterfaceMethod java/util/stream/Stream.map:(Ljava/util/function/Function;)Ljava/util/stream/Stream;
        24: invokestatic  #7                  // Method java/util/stream/Collectors.toList:()Ljava/util/stream/Collector;
        27: invokeinterface #8,  2            // InterfaceMethod java/util/stream/Stream.collect:(Ljava/util/stream/Collector;)Ljava/lang/Object;
        32: pop
        33: return
      LineNumberTable:
        line 13: 0
        line 14: 8
        line 16: 24
        line 17: 33
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            8      26     0  list   Ljava/util/List;
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
            8      26     0  list   Ljava/util/List<Ljava/lang/Integer;>;

private static java.lang.Boolean lambda$foo$2(java.lang.Integer);
...


А как это поможет в создании рантайма для динамически типизированных языков ? Ведь NameAndType фиксирован даже не как _аргумент_ инструкции (который можно на стек положить в зависимости от типа), а как параметр инструкции в байткоде ! То есть вот написали метод, который делает invokeDynamic для указанного типа+имени метода. А если тип другой ? И в каком месте выполнять свич по типам аргументов, фактически переданных методу ?
Re: Вопрос про invokedynamic
От: konsoletyper Россия https://github.com/konsoletyper
Дата: 01.06.15 09:44
Оценка:
Здравствуйте, elw00d, Вы писали:

E>А как это поможет в создании рантайма для динамически типизированных языков ? Ведь NameAndType фиксирован даже не как _аргумент_ инструкции (который можно на стек положить в зависимости от типа), а как параметр инструкции в байткоде ! То есть вот написали метод, который делает invokeDynamic для указанного типа+имени метода. А если тип другой ? И в каком месте выполнять свич по типам аргументов, фактически переданных методу ?


В общем-то, и раньше ничего не мешало делать динамические вызовы в JVM с использованием reflection. Просто reflection медленный, поэтому генерили по caller-классу на каждый метод. Так, можно было написать что-то в духе:

public void getMethod(String name) {
    switch (name) {
        case "foo":
            return new FooCaller();
        case "bar":
            return new BarCaller();
        // etc
    }
}


где все Caller'ы реализуют примерно такой интерфейс:

public interface Caller {
    Object call(Object... arguments);
}


и вызов, например, obj.foo() транслировать в obj.getMethod("foo").call(). А, соответственно, obj[methodName]() — в obj.getMethod(methodName).call(). Это значительно быстрее reflection, однако требует генерации огромного количества мелких классов. Специфика архитектуры JVM такова, что каждый новый класс — это довольно-таки много метаинформации, которая лежит мёртвым грузом в permgen (или просто в heap, начиная с Java 8). INVOKEDYNAMIC — это просто способ избавиться от этих накладных расходов. Вместо класса появляется новая сущность — dynamic call site'ы (который в первом приближении можно воспринимать как облегчённые классы), а сама инструкция INVOKEDYNAMIC — всего лишь декларативное описание алгоритма для порождения этих call site'ов. В итоге можно понаделать много мелких похожих друг на друга call site'ов, отличающихся лишь мелочами (например, именем вызываемого метода).

Можно было пойти по пути ускорения reflection, но у разработчиков интерпретаторов динамических языков есть очень много разных хотелок, которые трудно выразить простым reflection'ом, поэтому породили несколько более гибкий механизм.
Re[2]: Вопрос про invokedynamic
От: elw00d Россия http://elwood.su
Дата: 01.06.15 16:23
Оценка:
Здравствуйте, konsoletyper, Вы писали:

K>Здравствуйте, elw00d, Вы писали:


K>и вызов, например, obj.foo() транслировать в obj.getMethod("foo").call(). А, соответственно, obj[methodName]() — в obj.getMethod(methodName).call(). Это значительно быстрее reflection, однако требует генерации огромного количества мелких классов. Специфика архитектуры JVM такова, что каждый новый класс — это довольно-таки много метаинформации, которая лежит мёртвым грузом в permgen (или просто в heap, начиная с Java 8). INVOKEDYNAMIC — это просто способ избавиться от этих накладных расходов. Вместо класса появляется новая сущность — dynamic call site'ы (который в первом приближении можно воспринимать как облегчённые классы), а сама инструкция INVOKEDYNAMIC — всего лишь декларативное описание алгоритма для порождения этих call site'ов. В итоге можно понаделать много мелких похожих друг на друга call site'ов, отличающихся лишь мелочами (например, именем вызываемого метода).


K>Можно было пойти по пути ускорения reflection, но у разработчиков интерпретаторов динамических языков есть очень много разных хотелок, которые трудно выразить простым reflection'ом, поэтому породили несколько более гибкий механизм.


Спасибо, но вот как раз хотелось бы конкретики — я же пытаюсь разобраться на самом низком уровне как оно работает

Кстати, по докам вообще не очень понятно — что мы имеем на выходе этой инструкции. То ли она сама выполняет вызов метода, то ли она просто кладёт на вершину стека экземпляр какого-нибудь MethodHandle, и дальше надо его вызывать одним из «обычных» invoke-инструкций.
Re[3]: Вопрос про invokedynamic
От: konsoletyper Россия https://github.com/konsoletyper
Дата: 02.06.15 10:48
Оценка:
Здравствуйте, elw00d, Вы писали:

E>Спасибо, но вот как раз хотелось бы конкретики — я же пытаюсь разобраться на самом низком уровне как оно работает


Ну а что конкретика? Конкретика в спеках. А если на пальцах, то это уже 100 раз объясняли: при первом прохождении через invokedynamic вызывается bootstrap-метод с параметрами, указанными в дескрипторе callsite-а. Этот метод генерит CallSite, этот CallSite где-то запоминается. Далее, вызывается что-то вроде callSite.getTarget().invokeExact(аргументы из дескриптора callsite-а). При всех последующих прохождениях через callsite первая часть (т.е. bootstrap) не вызывается. Да, чтобы не путаться: дескриптор InvokeDynamic VM spec, 4.4.1 содержит описания двух методов: bootstrap и того, который потом вызывается в callsite'е.

E>Кстати, по докам вообще не очень понятно — что мы имеем на выходе этой инструкции. То ли она сама выполняет вызов метода, то ли она просто кладёт на вершину стека экземпляр какого-нибудь MethodHandle, и дальше надо его вызывать одним из «обычных» invoke-инструкций.


Всё сама. Про это чётко написано в спеках.

Кстати, немного оффтоп. Конечно, InvokeDynamic штука нужная, но напрочь убивает статический анализ (ну если только не привязываться к конкретным bootstrap-методам вроде LambdaMetafactory). Для случая с тем же LambdaMetafactory, могли бы предусмотреть другой механизм, для статических случаев, вроде тех же лямбд. А привязываться не всегда возможно — а ну как разработчики всяких Скал и Грувей понаизобретают своих LambdaMetafactory. И в Android InvokeDynamic не поддерживается. Да и вообще, какая-то макроинструкция, которая делает всё и сразу, понять и переварить её сложновато, а реализовать ещё сложнее. В общем, я так c осторожностью настроен к InvokeDynamic.
Re[3]: Вопрос про invokedynamic
От: rfq  
Дата: 03.06.15 14:09
Оценка:
Здравствуйте, elw00d, Вы писали:

E>Кстати, по докам вообще не очень понятно — что мы имеем на выходе этой инструкции. То ли она сама выполняет вызов метода, то ли она просто кладёт на вершину стека экземпляр какого-нибудь MethodHandle, и дальше надо его вызывать одним из «обычных» invoke-инструкций.


По крайней мере в случае изображения в программе лямбды, тело лямбды выносится в отдельный метод, а на месте текста лямбды вставляется invokedinamic, который кладет на стек ссылку на объект — обертку. Ссылку на обертку затем можно записать в переменную или передать параметром.
Re[4]: Вопрос про invokedynamic
От: elw00d Россия http://elwood.su
Дата: 04.06.15 14:57
Оценка:
Здравствуйте, rfq, Вы писали:

rfq>По крайней мере в случае изображения в программе лямбды, тело лямбды выносится в отдельный метод, а на месте текста лямбды вставляется invokedinamic, который кладет на стек ссылку на объект — обертку. Ссылку на обертку затем можно записать в переменную или передать параметром.


О, вот это пролило свет. Теперь понятнее, спасибо ! А то там сначала invokeDynamic, а потом вызов invokeInterface. Значит, первое на выходе возвращает экземпляр IFunction, а второй вызов передаёт этот IFunction в map().
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.