Разбираюсь с сабжем, и непонятно вот что. Везде пишут, что 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.
В случае статически типизированных лямбд всё понятно:
А как это поможет в создании рантайма для динамически типизированных языков ? Ведь NameAndType фиксирован даже не как _аргумент_ инструкции (который можно на стек положить в зависимости от типа), а как параметр инструкции в байткоде ! То есть вот написали метод, который делает invokeDynamic для указанного типа+имени метода. А если тип другой ? И в каком месте выполнять свич по типам аргументов, фактически переданных методу ?
Здравствуйте, 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'ом, поэтому породили несколько более гибкий механизм.
Здравствуйте, 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-инструкций.
Здравствуйте, 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.
Здравствуйте, elw00d, Вы писали:
E>Кстати, по докам вообще не очень понятно — что мы имеем на выходе этой инструкции. То ли она сама выполняет вызов метода, то ли она просто кладёт на вершину стека экземпляр какого-нибудь MethodHandle, и дальше надо его вызывать одним из «обычных» invoke-инструкций.
По крайней мере в случае изображения в программе лямбды, тело лямбды выносится в отдельный метод, а на месте текста лямбды вставляется invokedinamic, который кладет на стек ссылку на объект — обертку. Ссылку на обертку затем можно записать в переменную или передать параметром.
Здравствуйте, rfq, Вы писали:
rfq>По крайней мере в случае изображения в программе лямбды, тело лямбды выносится в отдельный метод, а на месте текста лямбды вставляется invokedinamic, который кладет на стек ссылку на объект — обертку. Ссылку на обертку затем можно записать в переменную или передать параметром.
О, вот это пролило свет. Теперь понятнее, спасибо ! А то там сначала invokeDynamic, а потом вызов invokeInterface. Значит, первое на выходе возвращает экземпляр IFunction, а второй вызов передаёт этот IFunction в map().