Код операции 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 можно найти здесь (с немного более сложной логикой). Если вы хотите узнать больше о том, почему я вижу эти инструкции, продолжайте читать.]

Я пишу компилятор пробелов, ориентированный на байт-код JVM. Несмотря на то, что это эзотерический язык, Whitespace описывает интересный набор инструкций по ассемблеру для стековой машины, который хорошо отображается на JVM.

Пробелы имеют метки, которые являются целями для перехода (goto/jump-if-zero/jump-if-negative) и вызовов функций. Соответствующие инструкции (с именами, данными мной, в спецификации они даны как комбинации пробела, табуляции и новой строки):

  • mark <label>: устанавливает метку для следующей инструкции
  • jump[-if-neg|-if-zero] <label>: безусловный или условный переход к заданной метке
  • call <label>: вызвать функцию, на которую указывает метка
  • end <label>: завершает функцию, возвращаясь к вызывающей стороне.

Мой компилятор выводит всю программу Whitespace в основном методе класса. Самый простой способ реализовать call и end — использовать коды операций JSR и RET, предназначенные для реализации подпрограмм. После операции JSR стек будет содержать ссылку returnAddress, которую следует сохранить в переменной для последующего использования в end.

Однако, поскольку mark может быть либо call, либо jump, стек может содержать или не содержать ссылку returnAddress. Я решил использовать логическую переменную (бит вызова по адресу 6), чтобы сохранить, как была достигнута отметка, а затем проверить, следует ли сохранять вершину стека в локальную переменную (адрес возврата, по адресу 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 (что было бы неправильно).. ., но if всегда переходит в конец. После вызова returnAddress остается в стеке, и следующая операция прерывается.

Я использовал анализатор ASM для просмотра стека для отладки всего этого, но только что подтвердил это поведение и не могу найти, что я делаю неправильно. Мое единственное подозрение состоит в том, что в iinc или в ifeq есть нечто большее, чем может вообразить моя тщеславная философия. Я признаю, что я только прочитал набор инструкций страницу и соответствующую документацию ASM для этого проекта, но я надеюсь, что кто-то может предложить решение из головы.

В этой папке находятся соответствующие файлы, включая исполняемый класс и исходный пробел , а также результаты анализа javap -c и ASM.


person Bruno Kim    schedule 03.04.2015    source источник
comment
В первом фрагменте вы переходите с помощью ifne, но во втором фрагменте с идентичным кодом вы переходите с помощью ifeq. Это намеренно? ifeq кажется правильным кодом операции для того, что вы хотите, так почему же его нет в первом фрагменте?   -  person Erwin Bolwidt    schedule 03.04.2015
comment
Спасибо, что заметили, исправил первый сниппет   -  person Bruno Kim    schedule 03.04.2015
comment
Второй вопрос: откуда вы знаете, что он всегда разветвляется? Вы пошагово проходите через байт-код? В противном случае вы, вероятно, наблюдаете каким-то косвенным образом, что также может быть проблемой.   -  person Erwin Bolwidt    schedule 03.04.2015
comment
Я слежу за выполнением, показанным в анализе ASM, который показывает состояние стека и переменных для каждого кода операции; а также пробовали отладку, ориентированную на printf, печатая ветку, которую взял код. Кроме того, ASM показывает, какие инструкции не были посещены символом ?. Но да, одношаговый байт-код был бы хорошим инструментом для использования.   -  person Bruno Kim    schedule 03.04.2015
comment
Я думал о странностях из-за инструкции RET, но вряд ли. Можете ли вы выдать байт-код для печати значения локальной переменной 6 непосредственно перед ifeq? Возможно, ваш инкремент/декремент не возвращает его обратно к нулю.   -  person Erwin Bolwidt    schedule 03.04.2015
comment
Частично сделал, добавил в папку. Однако я не вижу результата, так как JVM выдает ошибку VerifyError и ничего не выводит. Раньше я выходил из метода непосредственно перед ошибкой. Сейчас пытаюсь сделать то же самое.   -  person Bruno Kim    schedule 03.04.2015
comment
Давайте продолжим обсуждение в чате.   -  person Bruno Kim    schedule 03.04.2015


Ответы (1)


Нашел возможную причину: проблема не при выполнении, а с верификатором. Когда казалось, что он «всегда разветвляется», на самом деле верификатор проверял все возможные результаты if, поэтому он мог быть уверен, что стек будет выглядеть одинаково. Мой код опирается на ссылку (returnAddress), которая может присутствовать или отсутствовать в стеке, и верификатор не может это проверить.

Тем не менее, пример кода не запускается с флагом -noverify, но другие, более простые примеры, не прошедшие проверку, выполнялись правильно.

person Bruno Kim    schedule 06.04.2015
comment
Имейте в виду, что здесь вы едете на дохлой лошади. Начиная с версии файла класса 51 инструкции jsr и ret больше не поддерживаются. Нежелание проверять код, который может быть как переходным, так и вызываемым, было мотивом для удаления этой функции… - person Holger; 08.04.2015
comment
Спасибо за вклад, я переделываю свой компилятор, чтобы он не зависел от этих инструкций. Я начал с этого, думая, что байт-код JVM был простым и дзен, и мальчик, как много я учусь! - person Bruno Kim; 08.04.2015