Код операции 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 флаг, но другие, более простые примеры, которые не прошли проверку, выполнялись правильно.

Другие вопросы по тегам