Подтвердить что ты не робот

Типы в байткоде

Я работал некоторое время на (Java) Bytecode, однако мне никогда не приходило в голову спросить, почему вводятся некоторые инструкции? Я понимаю, что в операции ADD нам нужно различать целочисленное добавление и добавление FP (почему у нас есть IADD и FADD). Однако почему нам нужно различать ISTORE и FSTORE? Они оба связаны с одной и той же операцией, которая перемещает 32 бита из стека в локальную переменную?

Единственный ответ, который я могу придумать, - это безопасный тип, чтобы предотвратить это: (ILOAD, ILOAD, FADD). Тем не менее, я считаю, что безопасность типов уже внедрена на уровне языка Java. ОК, формат файла класса напрямую не связан с Java, так ли это способ обеспечения безопасности типов для языков, которые его не поддерживают? Любая мысль? Спасибо.

РЕДАКТИРОВАТЬ:, чтобы отслеживать ответ Риди. Я написал эту минимальную программу:

public static void main(String args[])
{
    int x = 1;
}

который скомпилирован для:

iconst_1
istore_1
return

используя редактор байт-кода, я изменил вторую инструкцию:

iconst_1
fstore_1
return

и он возвратил java.lang.VerifyError: ожидая найти float в стеке.

Интересно, если в стеке нет информации о типе, только битах, как команда FSTORE знала, что она имеет дело с int, а не с плавающей точкой?

Примечание: Я не мог найти лучшего заголовка для этого вопроса. Не стесняйтесь улучшать его.

4b9b3361

Ответ 1

Эти инструкции печатаются, чтобы гарантировать, что программа является типичной. При загрузке класса виртуальная машина выполняет проверку на байт-кодах, чтобы гарантировать, что, например, float не передается в качестве аргумента методу, ожидающему целое число. Эта статическая проверка требует, чтобы верификатор мог определять типы и количество значений в стеке для любого заданного пути выполнения. Инструкции по загрузке и хранению требуют тега типа, поскольку локальные переменные в кадрах стека не печатаются (т.е. Вы можете использовать локальную переменную и позже fstore в той же позиции). Тег типа в инструкциях позволяет верификатору знать, какой тип значения хранится в каждой локальной переменной.

Верификатор просматривает каждый код операции в методе и отслеживает, какие типы будут в стеке и в локальных переменных после выполнения каждого из них. Вы правы, что это еще одна форма проверки типов и дублирует некоторые проверки, выполняемые java-компилятором. Этап проверки предотвращает загрузку любого кода, который заставит виртуальную машину выполнять незаконную инструкцию и обеспечивает безопасность свойств платформы Java, не прибегая к большому времени выполнения проверки типов проверки перед каждой операцией. Проверка типа времени выполнения для каждого кода операции будет производительностью при каждом запуске метода, но статическая проверка выполняется только один раз при загрузке класса.

Случай 1:

Instruction             Verification    Stack Types            Local Variable Types 
----------------------- --------------- ---------------------- ----------------------- 
<method entry>          OK              []                     1: none
iconst_1                OK              [int]                  1: none
istore_1                OK              []                     1: int
return                  OK              []                     1: int

Случай 2:

Instruction             Verification    Stack Types            Local Variable Types 
----------------------- --------------- ---------------------- ----------------------- 
<method entry>          OK              []                     1: none
iconst_1                OK              [int]                  1: none
fstore_1                Error: Expecting to find float on stack

Ошибка указана, потому что верификатор знает, что fstore_1 ожидает float в стеке, но результат выполнения предыдущих инструкций оставляет int в стеке.

Эта проверка выполняется без выполнения кодов операций, а это делается путем просмотра типов команд, так же, как компилятор java дает ошибку при написании (Integer)"abcd". Компилятору не нужно запускать программу, чтобы знать, что "abcd" является строкой и не может быть добавлена ​​в Integer.

Ответ 2

Джефф Риди объяснил в своем ответе, что делает верификатор, когда класс загружен. Я просто хочу добавить, что вы можете отключить верификатор с помощью параметра JVM. Это не рекомендуется!

Для вашей примерной программы (с iconst и fstore) результатом запуска с отключенной верификацией является ошибка VM, которая останавливает JVM со следующим сообщением:

=============== DEBUG MESSAGE: illegal bytecode sequence - method not verified ================

#
# An unexpected error has been detected by HotSpot Virtual Machine:
#
#  EXCEPTION_PRIV_INSTRUCTION (0xc0000096) at pc=0x00a82571, pid=2496, tid=3408
#
# Java VM: Java HotSpot(TM) Client VM (1.5.0_15-b04 mixed mode, sharing)
# Problematic frame:
# j  BytecodeMismatch.main([Ljava/lang/String;)V+0
#
...

Ответ 3

Чтобы ответить на ваш первый вопрос с моей наилучшей догадкой: эти байткоды разные, потому что им могут потребоваться разные реализации. Например, конкретная архитектура может содержать целые операнды в основном стеке, но операнды с плавающей запятой в аппаратных регистрах.

Чтобы ответить на ваш второй вопрос, VerifyError вызывается, когда класс загружается, а не когда он выполняется. Процесс проверки описан здесь; примечание № 3.

Ответ 4

Все байт-коды должны быть явно определенными со статическим анализом потока данных, как указано выше. Однако на самом деле это не объясняет, почему такие инструкции, как _store, имеют разные типы, поскольку тип может быть выведен из типа значения в стеке. Фактически, есть некоторые инструкции, такие как pop, dup и swap, которые делают именно это и работают на нескольких типах. Почему некоторые инструкции печатаются, а другие - это то, что может быть объяснено только оригинальными разработчиками Java.