Использование invokedynamic — что происходит под капотом?

Задний план

В настоящее время я пишу JVM на С# исключительно для академических целей (и, возможно, для создания смешанного приложения .NET и Java/Scala в будущем).

Контекст

Я пишу простой класс JAVA:

public class test
{
    public static String hello_world(int i)
    {
        return "Hello " + i + " World!";
    }
}

И скомпилируйте его в test.class. Когда я декомпилирую его своим декомпилятором (который я написал как часть JVM), я вижу следующие инструкции для этого метода:

iload_0
invokedynamic 2
areturn

Просматривая пул констант для константы с индексом 2, я вижу запись InvokeDynamic-Constant со следующими данными:

makeConcatWithConstants : (I)Ljava/lang/String;

Думаю, это имеет смысл (я больше пользователь .NET, чем пользователь JAVA).

При выполнении моего метода hello_world с параметром 1 перед выполнением invokedynamic 2 у меня есть следующий стек:

----TOP---
0x00000001
--BOTTOM--

Вопрос

Мой вопрос: Как мне использовать invokedynamic?
Я не могу разрешить метод makeConcatWithConstants, так как InvokeDynamic-Constant не дает мне никакой подсказки, где может находиться makeConcatWithConstants (см. документацию).
Стек также не содержит ссылки на кучу, указывающую, с каким типом экземпляра может быть связан метод makeConcatWithConstants.

Я прочитал документы invokedynamic но я этого не понимаю (может быть, я слишком "поврежден" .NET-Framework).

Может ли кто-нибудь указать мне на какой-нибудь пример того, что происходит под капотом JVM при выполнении этих трех инструкций? (Что ожидает вызываемый invokedynamic и т. д.)?

Я уже реализовал invokestatic в своей JVM... но пока не могу понять invokedynamic.


person unknown6656    schedule 04.11.2018    source источник
comment
Интересно. Я не знал, что современные компиляторы Java используют invokedynamic для оптимизации конкатенации строк. Это гениально!   -  person Jörg W Mittag    schedule 04.11.2018
comment
@JörgWMittag stackoverflow. ком/вопросы/46512888/   -  person Thilo    schedule 04.11.2018
comment
Хм. Я бы рассмотрел invokedynamic docs, вы связались сами с собой, что довольно исчерпывающе, особенно когда вы также переходите по ссылке. Возможно, полезно прочитать документацию пакета в дополнение.   -  person Holger    schedule 04.11.2018
comment
В качестве дополнительного примечания: когда вы собираетесь обрабатывать файлы классов, которые явно скомпилированы с помощью Java 9 или новее, вам не следует продолжать использовать документацию по Java 7. См. спецификацию JVM для Java 11...   -  person Holger    schedule 04.11.2018
comment
@Holger: я знаю, что документы довольно исчерпывающие, однако лично мне трудно следовать им в полной мере, поскольку я пришел из мира .NET. Спасибо, что направили меня к документам JVMv11, что, конечно же, объясняет, почему у меня возникли проблемы с анализом новых файлов .class при использовании старых спецификаций....   -  person unknown6656    schedule 04.11.2018


Ответы (1)


Идея invokedynamic такова; При первом столкновении с этим байт-кодом вызовите метод начальной загрузки, который создает объект Callsite, который ссылается на фактический метод, который необходимо вызвать.

На практике это часто означает, что вы динамически создаете реализацию вызова.

Если вы посмотрите на свою программу с javap -v test, вы увидите внизу атрибут BootstrapMethods:

BootstrapMethods:
  0: #15 REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #16 Hello \u0001 World!

Где вы можете видеть, что метод начальной загрузки для этого конкретного сайта вызова находится в StringConcatFactory

Method arguments — это набор постоянных аргументов.

Ведущие аргументы Lookup, String и MethodType соответственно; объект поиска с теми же привилегиями, что и у сайта вызова, некоторым именем и типом сайта вызова. Первый из них должен быть предоставлен виртуальной машиной во время выполнения, а последние 2 предоставляются записью пула переменных invokedynamic в виде имени и типа:

#2 = InvokeDynamic      #0:#17         // #0:makeConcatWithConstants:(I)Ljava/lang/String;

Таким образом, чтобы реализовать этот байт-код, вы должны иметь некоторый механизм для создания объекта поиска, а затем иметь возможность вызывать метод начальной загрузки. После этого вы можете вызвать dynamicInvoker() на возвращенном Callsite, который дает вам MethodHandle, который затем следует кэшировать для этого конкретного сайта вызова, а затем (наконец) вызывать.

Если вы хотите посмотреть, как это реализовано в OpenJDK, вы можете найти реализацию здесь: http://hg.openjdk.java.net/jdk/jdk/file/tip/src/hotspot/share/interpreter/bytecodeInterpreter.cpp#l2446

Я предполагаю, что это, вероятно, слишком сложно на этой ранней стадии проекта, поэтому на данный момент может быть проще скомпилировать вашу программу с -XDstringConcat=inline, поскольку при этом используется устаревшая конкатенация StringBuilder, которая должна быть проще в реализации.

person Jorn Vernee    schedule 04.11.2018
comment
Большое спасибо за ваш содержательный ответ! Я постараюсь реализовать это создание callsite и вернусь сюда, чтобы сообщить о моем будущем прогрессе/ошибках/вопросах. - person unknown6656; 04.11.2018
comment
По сути, это программируемая пользователем версия invokevirtual. На самом деле, когда у вас есть invokedynamic, вы сможете реализовать все более invoke* байт-кода поверх него. invokedynamic был введен, чтобы позволить разработчикам других языков, отличных от Java, пользоваться теми же видами производительности для их динамической диспетчеризации, что и Java. - person Jörg W Mittag; 04.11.2018
comment
Есть один случай, когда вы не можете использовать invokedynamic вместо одного из других байт-кодов invoke*: вызов суперконструктора в конструкторе. Это должна быть инструкция invokespecial. - person Johannes Kuhn; 02.09.2019