JEP 303: Intrinsics for the LDC and INVOKEDYNAMIC Instructions
Intrinsic-методы для инструкций LDC и INVOKEDYNAMIC
| Автор | Brian Goetz |
| Ответственный | Vicente Arturo Romero Zaldivar |
| Тип | Feature |
| Область | SE |
| Статус | Candidate |
| Компонент | tools / javac |
| Обсуждение | valhalla dash dev at openjdk dot java dot net |
| Трудоёмкость | M |
| Длительность | M |
| Зависит от | JEP 334: JVM Constants API |
| Связан с | JEP 309: Dynamic Class-File Constants |
| Создан | 2017/04/07 14:01 |
| Обновлён | 2018/09/11 15:05 |
| Задача | 8178320 |
Аннотация
Предоставить библиотечную поддержку в виде intrinsic-методов, с которой Java-разработчики смогут писать исходный код на Java, надёжно транслируемый в вызовы INVOKEDYNAMIC, и так напрямую обращаться к функциональности, реализованной через bootstrap-методы invokedynamic. Предоставить возможность лениво инициализировать и создавать сущности времени выполнения (например, Class и MethodHandle), выразимые как константы JVM, без потери производительности. Расширить распространение констант на типы-дескрипторы для соответствующих типов записей пула констант, таких как Class, MethodType и MethodHandle.
Что не является целью
Целью не является предоставить механизм для представления всех возможных байт-кодов, дать детальный контроль над генерацией кода или предоставить спецификацию трансляции исходного кода на Java в class-файлы Java. Целью не является расширить обработку атрибута ConstantValue для поддержки более широкого встраивания констант между единицами компиляции. Крайне нежелательно, чтобы эта возможность вводила новый синтаксис языка.
Мотивация
JSR-292 добавил байт-код invokedynamic («indy») и несколько новых типов пула констант для представления типов методов и дескрипторов методов (method handles). Изначально эти средства предназначались для динамически типизированных языков, но статически типизированные языки, такие как Java, тоже нашли способы извлечь из них пользу (и лямбда-преобразование, и конкатенация строк в javac транслируются с помощью indy, и ожидается более широкое применение). По мере того как всё больше функциональности времени выполнения выражается через bootstrap-методы indy, невозможность обратиться к этой функциональности из исходного кода на Java (в том числе для тестирования этих bootstrap-методов) всё сильнее становится препятствием. (Проблема станет ещё острее с появлением «constant dynamic» (condy), которому посвящён отдельный JEP.)
Код, использующий новые формы пула констант — MethodType и MethodHandle, — обычно инициализировал их статически, чтобы избежать затрат на ленивую инициализацию, но платой за это становится дополнительная работа (включая загрузку классов) во время инициализации класса. В JVM уже есть механизм для эффективной ленивой инициализации и интернирования общих констант — пул констант. Если дать средство номинально описывать константы и загружать их через LDC, Java-разработчики смогут использовать этот механизм напрямую.
Кроме того, номинальная форма для сложных типов пула констант в любом случае нужна для API работы с байт-кодом и API плагинов компилятора, которым часто приходится каждый раз заново изобретать её, как, например, класс Handle в ASM. (И опять же, эта проблема станет острее с появлением «constant dynamic».)
Описание
Для каждого объекта, который можно описать записью пула констант, нам нужен объект-компаньон, описывающий эту запись только через другие константы (как это делает пул констант), причём имена классов интерпретируются относительно загрузчика того класса, в котором они разрешаются. (Это означает, что можно создать описание для незагруженного класса и что создание дескриптора не вызывает загрузку классов.) Некоторые формы пула констант (такие как Integer или String) могут служить собственным дескриптором; для более сложных констант (Class, MethodType) нужен явный дескриптор.
public interface Constable<T> {
T resolveConstant(MethodHandles.Lookup lookup) throws ReflectiveOperationException;
}
public static final class ClassConstant implements Constable<Class> {
private final String descriptor;
private ClassConstant(String descriptor) { ... }
public static ClassConstant of(String descriptor) {
return new ClassConstant(descriptor);
}
public String descriptorString() {
return descriptor;
}
public Class resolveConstant(MethodHandles.Lookup lookup) throws ReflectiveOperationException { ... }
}
Создание ClassConstant лишь проверяет структуру дескриптора и сохраняет его; загрузка классов не происходит. Более сложные дескрипторы констант, такие как MethodTypeConstant, используют ClassConstant для описания типов параметров и возвращаемого значения, а MethodHandleConstant использует ClassConstant и MethodTypeConstant, так что сложные дескрипторы «номинальны на всех уровнях».
Распространение констант
Мы расширяем набор переменных и выражений, которые считаются константными выражениями (CE); для этих константных выражений компилятор отслеживает и распространяет их значения во время компиляции и в итоге может использовать эти распространённые константы, когда они передаются как аргументы intrinsic-методам. Мы не выполняем никакой новой свёртки констант — мы лишь отслеживаем значения любых выражений, являющихся CE по следующим правилам, и если CE используется как аргумент intrinsic-метода, в точке замены на intrinsic используется значение константы.
- Литералы int, long, float, double и String являются CE.
- Поля static final, инициализатор которых является CE, являются CE.
- Фактически финальные (effectively final) локальные переменные, инициализатор которых является CE, являются CE.
- Для подходящего набора статических фабричных методов в
XxxConstant, если все аргументы являются CE, результат является CE. Например, если мы вызываемClassConstant.of()с CE-аргументом `"Lcom/Foo;", результат является CE. - Для подходящего набора методов экземпляра в
XxxConstant, если все аргументы являются CE и получатель является CE, результат является CE. Например, если мы создаёмMethodHandleConstantчерез фабрику только с константными входными данными, а затем вызываем его методtype(), результатом будет CE типаMethodTypeConstant.
Замена LDC на intrinsic
Будет некоторый метод, соответствующий байт-коду LDC:
class Intrinsics {
public static<T> T ldc(Constable<T> constant) { ... }
}
который компилятор будет обрабатывать как intrinsic, то есть заменит вызов метода настоящим байт-кодом LDC с операндом, соответствующим константе, описанной constant. Если переданный Constable не является CE, это будет ошибкой компиляции (а компилятор может выдавать предупреждения при компиляции, если эта константа или любая из констант, на которые она косвенно ссылается, описывает класс или член, отсутствующий в class path времени компиляции). Эти intrinsic-методы, скорее всего, нельзя будет вызвать через рефлексию.
Дескрипторы invokedynamic
Bootstrap-метод invokedynamic описывается структурой, похожей на XxxConstant, для которой компилятор также обеспечивает распространение CE:
public static final class BootstrapSpecifier {
final MethodHandleConstant bsm;
final String invocationName;
final MethodTypeConstant invocationDesc;
final Constable<?>[] bsmArgs;
private BootstrapSpecifier(MethodHandleConstant bsm, String invocationName, MethodTypeConstant invocationDesc, Constable<?>... bsmArgs) {
this.bsm = bsm;
this.invocationName = invocationName;
this.invocationDesc = invocationDesc;
this.bsmArgs = bsmArgs;
}
public static BootstrapSpecifier of(MethodHandleConstant bsm, String invocationName, MethodTypeConstant invocationDesc, Constable<?>... bsmArgs) {
return new BootstrapSpecifier(bsm, invocationName, invocationDesc, bsmArgs);
}
}
Замена invokedynamic на intrinsic
Аналогично ldc(), существует intrinsic-метод для invokedynamic:
class Intrinsics {
@PolymorphicSignature
public static Object invokedynamic(BootstrapSpecifier indy, Object... args) { return null; }
}
И здесь аргумент indy должен быть CE, иначе это ошибка компиляции. Компилятор заменяет вызовы invokedynamic() настоящей инструкцией invokedynamic, описываемой записью BootstrapMethods, соответствующей BootstrapSpecifier. Дескриптор вызова копируется из дескриптора BootstrapSpecifier, а типы аргументов и возвращаемых значений проверяются и адаптируются по этой сигнатуре.
Альтернативы
В JDK 7 изучалась прямая синтаксическая поддержка indy, но её отвергли, поскольку она усложняла язык ради сценария, нужного лишь ничтожному меньшинству Java-разработчиков.
Мы также рассматривали детерминированную свёртку констант, при которой компилятор мог бы заменять на intrinsic, распространять и сворачивать инициализацию объектов Class и MethodHandle из константных входных данных, но отвергли этот подход, поскольку момент возникновения побочных эффектов был недостаточно прозрачен.
Зависимости
Эта возможность взаимодействует с «constant dynamic»; когда он станет доступен, потребуется дополнительная поддержка динамических констант.
Эта возможность, вероятно, будет полезна для Isolated Methods (JDK-8158765).