Код операции ifeq/ifne JVM всегда ветвится
[TL;DR: следующие инструкции байт-кода JVM не работают:
iconst_0
istore 6
...sequential
iinc 6 1
jsr L42
...
; L42
iload 6
ifeq L53 ; Always branches!!!
astore 8
iinc 6 -1
; L53
LDC 100
ISUB ; ERROR, returnAddress is at the top of the stack
Тестовый.class можно найти здесь (с немного более сложной логикой). Если вы хотите узнать больше о том, почему я вижу эти инструкции, пожалуйста, продолжайте читать.]
Я пишу компилятор Whitespace для байт-кода JVM. Несмотря на то, что Whitespace является эзотерическим языком, он описывает интересный набор инструкций по сборке для стекового компьютера, который хорошо отображается в JVM.
Пробельные символы имеют метки, которые являются одновременно целями для перехода (goto/jump-if-zero/jump-if-отрицательный) и вызовов функций. Соответствующие инструкции (с именами, данными мной, в спецификации они указаны как комбинации пробела, табуляции и новых строк):
mark <label>
: устанавливает метку для следующей инструкцииjump[-if-neg|-if-zero] <label>
: переход безоговорочно или условно к данной меткеcall <label>
: вызвать функцию, указанную меткойend <label>
: завершает функцию, возвращаясь к вызывающей стороне.
Мой компилятор выводит всю программу Whitespace в основной метод класса. Самый простой способ реализовать call
а также end
использует JSR
а также RET
коды операций, которые сделаны для реализации подпрограмм. После JSR
операция стек будет содержать returnAddress
ссылка, которая должна храниться в переменной для последующего использования в end
,
Тем не менее, как mark
может быть call
или jump
в стек может содержать или не содержать returnAddress
ссылка. Я решил использовать булеву переменную (call-bit, по адресу 6), чтобы сохранить то, как была достигнута метка, и затем проверить, должна ли она хранить вершину стека в локальной переменной (return-address, по адресу 8). Реализация для каждой инструкции выглядит следующим образом:
; ... initialization
iconst_0
istore 6 ; local variable #6 holds the call bit
# call
iinc 6 1 ; sets the call bit
jsr Lxxx ; jumps to the given label, pushing a returnAddress to the stack
# mark
; Lxxx
iload 6 ; loads the call bit
ifeq Lxxx-end ; SHOULD jump to mark's end if the call bit is not set
; call bit is set: mark was call-ed and returnAddress is in the stack
astore 8 ; stores returnAddress to local variable #8
iinc 6 -1 ; resets the call bit
; Lxxx-end
# end
ret 8 ; returns using the stored returnAddress
Эта проблема: ifeq
ВСЕГДА филиалы. Я также попытался изменить логику (call-bit -> jump-bit, ifeq->ifne) и даже просто переключиться на ifne
(что было бы неправильно)... но если всегда ветвится до конца. После звонка returnAddress
остается в стеке, и следующая операция взрывается.
Я использовал анализатор ASM для наблюдения за стеком, чтобы отлаживать все это, но только что утвердил это поведение и не могу найти, что я делаю неправильно. Мое единственное подозрение, что это еще не все iinc
или ifeq
чем может представить моя тщетная философия. Я признаю, что я только прочитал страницу с набором инструкций и соответствующую документацию ASM для этого проекта, но я надеюсь, что кто-то может принести решение из головы.
В этой папке находятся соответствующие файлы, включая исполняемый класс и исходные пробелы, а также вывод javap -c
и анализ ASM.
1 ответ
Нашел возможную причину: проблема не во время выполнения, а с верификатором. Когда казалось, что он "всегда разветвлен", он фактически проверял все возможные результаты if
поэтому можно быть уверенным, что стек будет выглядеть одинаково. Мой код опирается на ссылку (returnAddress
) может быть, а может и не присутствовать в стеке, и верификатор не может это проверить.
Тем не менее, пример кода не работает с -noverify
флаг, но другие, более простые примеры, которые не прошли проверку, выполнялись правильно.