JEP draft: Extended Opcodes
Расширенные коды операций
| Автор | Julian Waters |
| Тип | Infrastructure |
| Область | SE |
| Статус | Draft |
| Компонент | specification / vm |
| Обсуждение | hotspot dash dev at openjdk dot java dot net |
| Трудоёмкость | M |
| Создан | 2022/03/17 02:21 |
| Обновлён | 2025/08/18 00:00 |
| Задача | 8283291 |
Аннотация
Реализовать изменения для поддержки более широкого набора возможных кодов операций JVM, не нарушая совместимость между классами, использующими существующие коды операций длиной 1 байт. Переименовать байт-код JVM в «opcode» (код операции), чтобы подчеркнуть, что инструкции JVM больше не ограничены одним байтом.
Цели
Этот JEP призван высвободить дополнительные коды операций за пределами произвольного ограничения размером в один байт, освободив место для новых инструкций, которые будут использоваться в будущих проектах и улучшениях JVM (и, соответственно, изменить спецификацию JVM, чтобы разрешить эти новые инструкции и дать реализациям больше свободы)
Что не является целью
В рамках этого JEP не ставится цель заменить или изменить какие-либо из существующих кодов операций или добавить новые.
Критерии успеха
Производительность JVM при выполнении любых существующих или новых кодов операций во время работы не снижается при добавлении новых инструкций (в качестве общего примера — поддержки типов для каждой инструкции const), а новые коды операций можно легко добавлять в среду выполнения, не беспокоясь о механизмах их хранения при компиляции в коды операций JVM.
Мотивация
Сейчас виртуальная машина Java ограничена одним байтом инструкций. Это оставляет реализациям всего 256 кодов операций. Из них примерно 202 уже используются и 3 зарезервированы для особых случаев, так что на будущее остаётся всего 55. Это создаёт давление и ряд ограничений для любой работы, в которой набор инструкций JVM, возможно, придётся изменить ради новых языковых конструкций или попыток оптимизации на уровне байт-кода, и в ряде случаев вынуждает принимать в качестве окончательного решения альтернативные и менее оптимальные подходы. Поэтому JVM выиграла бы от большего числа доступных кодов операций: это дало бы свободу, когда ведётся работа, которая может потребовать добавления новых инструкций в HotSpot.
Однако при этом в спецификации ясно указана причина, по которой коды операций решили ограничить одним байтом:
«Решение ограничить код операции виртуальной машины Java одним байтом и отказаться от выравнивания данных в скомпилированном коде отражает сознательный выбор в пользу компактности, возможно, ценой некоторой потери производительности в наивных реализациях. Однобайтовый код операции также ограничивает размер набора инструкций. Отсутствие предположения о выравнивании данных означает, что на многих машинах непосредственные данные размером больше байта приходится собирать из байтов во время выполнения.»
С учётом этого следует разработать решение, которое даст возможность расширять коды операций JVM, но в остальных случаях использовать только уже существующие коды операций длиной 1 байт.
Описание
Как было предложено во внутреннем обсуждении (и впоследствии принято вместо прежнего подхода, который описан ниже в разделе альтернатив), мы зарезервируем один из пока не используемых кодов операций (на момент написания 0xCB–0xFD) под значение «extend», сообщающее виртуальной машине Java, что следующий за ним код операции является расширенным. Это похоже на то, как работает существующий код операции wide:
wide:
opcode, indexbyte1, indexbyte2 или iinc, indexbyte1, indexbyte2, countbyte1, countbyte2
выполнить opcode, где opcode — это iload, fload, aload, lload, dload, istore, fstore, astore, lstore, dstore или ret, но считать индекс 16-битным; или выполнить iinc, где индекс 16-битный, а константа приращения — 16-битное знаковое short
Код операции, следующий непосредственно за инструкцией «extend», интерпретировался бы иначе. Например, если взять инструкцию arraylength (0xBE) и воображаемую расширенную инструкцию 0x01BE, виртуальная машина Java прочитала бы 0xBE как обычную arraylength, если перед ней нет кода операции «extend», но если «extend» перед ней есть, она была бы прочитана как 0x01BE. Интересная возможность, которую это открывает, — объединение кодов операций extend в цепочки, например следующая последовательность
extend extend extend 0xBE
фактически будет прочитана как 0x03BE. Как отмечалось в обсуждении, это как минимум высвобождает 256 новых кодов операций ценой всего одного, с минимальным влиянием на объём и на тех, кто создаёт или потребляет коды операций.
Альтернативы
Как вариант, можно просто сохранить однобайтовые коды операций и работать с их ограничениями, однако это, в свою очередь, часто мешает развитию набора инструкций. Можно также просто сделать все коды операций длиной x байт, но это противоречит изначальной причине, по которой коды операций могли иметь длину только 1 байт, и к тому же (скорее всего) нарушит совместимость с существующими class-файлами.
Как предлагалось в прежней версии этого JEP, можно сделать следующее:
«Вместо того чтобы заменять существующие коды операций длиной 1 байт в атрибуте code для MethodInfo, что потребовало бы дополнять каждый существующий код операции байтами 0x00 спереди, мы вводим понятие расширенных кодов операций (Extended Opcodes). Расширение кода операции (Opcode Extension) — это когда один байт в Code_attribute дополняется спереди произвольным числом байтов. Это достигается хранением байтов расширения только для определённых кодов операций, так что обычные инструкции длиной 1 байт хранятся как обычно, и представление остаётся компактным.
Рассмотрим воображаемый код операции 0xCAFEBABE, который на 3 байта длиннее, чем сейчас возможно в любой JVM. При этом 0xBE сам по себе — код операции arraylength. Этот JEP предлагает хранить код операции произвольной длины 0xCAFEBABE следующим образом:
0xBE, младший байт инструкции, хранится в последовательности инструкций, как любой другой обычный код операции. Затем сохраняется связанная запись расширения, в которой 0xCAFEBA хранится в правильном порядке в виде отдельных байтов 0xCA, 0xFE и 0xBA. Эта запись также будет содержать индекс в code[], по которому находится соответствующий расширяемый ею байт. При загрузке класса JVM просканирует список в поисках расширенных кодов операций, найдёт их и затем заменит код операции по этому индексу байтами, хранящимися в записи расширения, после чего он будет выполняться как обычно. Если бы соответствующей записи расширенного кода операции для 0xBE при загрузке не оказалось, он был бы интерпретирован как обычная инструкция arraylength. Это позволяет class-файлам оставаться компактными, поскольку дополнительные байты хранятся только для расширенных кодов операций, а уже существующие по-прежнему представимы одним байтом.
Для этого мы введём новый атрибут ExtendedOpcodes для атрибута Code, используя уже имеющийся, но не используемый раздел атрибутов, который уже содержится в Code_attribute.
ExtendedOpcodes имеет следующую структуру:
ExtendedOpcodes_attribute { u2 attribute_name_index; u4 attribute_length; u4 extendedOpcodeCount; { u4 counter; u1 count; u1 extensions[count]; } extendedOpcodes[extendedOpcodeCount]; }
Где:
Значение элемента attribute_name_index должно быть допустимым индексом в таблице constant_pool. Запись constant_pool по этому индексу должна быть структурой CONSTANT_Utf8_info, представляющей строку «ExtendedOpcodes».
attribute_length содержит длину атрибута без учёта начальных 6 байт
extendedOpcodeCount — число кодов операций в code[], расширенных с помощью этой новой системы; на него распространяются те же ограничения, что и на code_length в Code_attribute
extendedOpcodes содержит набор всех расширенных кодов операций в содержащем его Code_attribute. Каждая запись этого набора содержит:
counter — допустимый индекс одного байта в наборе code[] родительского Code_attribute
count — число байтов, которыми может быть расширен целевой код операции. Например, count, равный 1, означает добавление до 0xFF перед соответствующим кодом операции в Code_attributes, count, равный 2, означает, что код операции может быть расширен спереди максимум до 0xFFFF, и так далее.
Байты в extensions[], от первого к последнему, составляют всё значение, которым целевой код операции расширяется спереди. Например, если инициализировать extensions значением {0xCA, 0xFE, 0xBA} для кода операции 0xBE, получится полная инструкция 0xCAFEBABE.
Важно, что этот атрибут полностью необязателен, и если атрибут расширенных кодов операций отсутствует в атрибуте code, все коды операций в нём просто считаются обычными кодами операций длиной 1 байт, что сохраняет полную совместимость с class-файлами, не содержащими расширенных кодов операций. Это также позволяет сохранить компактность существующих кодов операций без необходимости дополнять их пустыми байтами, а именно это и было основной причиной, по которой коды операций изначально ограничили одним байтом. Даже если в последовательности инструкций есть расширенные коды операций, дополнительные байты хранятся только для тех, которые были расширены, что в итоге позволяет экономить место в class-файле. Надеемся, что по мере продвижения работы удастся разработать более компактный способ хранения, чем представленный в этом JEP.»
Однако после некоторого обсуждения этот подход был признан несколько громоздким и требующим слишком больших изменений в существующей структуре загрузчиков классов и во время выполнения, когда исполняются коды операций. Этот подход по-прежнему приведён здесь как альтернатива на случай, если он окажется полезен в будущих обсуждениях
Тестирование
У этого JEP нет особых аспектов, связанных с платформой или оборудованием. Единственным тестированием будет проверка того, можно ли после реализации корректно расширять новые коды операций без снижения производительности.
Риски и допущения
Этот JEP предполагает, что шаблонный интерпретатор (Templating Interpreter) JVM и компиляторы байт-кода могут работать с кодами операций произвольной длины, что выполнение кодов операций длиннее одного байта не окажет существенного негативного влияния на производительность и что единственным препятствием является компактность class-файлов.
Зависимости
Сейчас нет JEP, от которых зависит этот JEP или которые зависят от него.