2015-04-03 3 views
3

[TL; DR: следующие инструкции виртуальной машины Java байт-кодов, похоже, не работает:ifeq/ifne виртуальной машины Java опкод всегда ветви

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

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

Простые пробелы имеют метки, которые являются одновременно мишенями для перехода (переключение/переход-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 -ed или jump -ed в стек может или не может содержать 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 ВСЕГДА ветвь. Я также попытался изменить логику (бит вызова -> бит-бит, ifeq-> ifne) и даже просто переключиться на ifne (что было бы неправильно) ... но если всегда ветви до конца. После вызова returnAddress остается в стеке, и следующая операция взрывается.

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

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

+0

В вашем первом фрагменте вы прыгаете с 'ifne', но во втором фрагменте кода, с другим идентичным кодом, вы прыгаете с' ifeq'. Это намеренно? 'ifeq' кажется правильным кодом операции для чего вы хотите, так почему же он не в первом фрагменте? –

+0

Спасибо, что заметили, исправили первый фрагмент –

+0

Второй вопрос: откуда вы знаете, что он всегда ветви? Вы один шаг по байт-коду? В противном случае вы, вероятно, наблюдаете косвенным образом - и это также может быть проблемой. –

ответ

0

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

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

+0

Имейте в виду, что вы едете на мертвой лошади здесь. Начиная с версии файла класса 51 команды 'jsr' и' ret' больше не поддерживаются. Не желая проверять код, который может быть и тем, и другим, * перекодировал * и * вызвал * к, был мотивацией для удаления этой функции ... – Holger

+0

Спасибо за ввод, я перерабатываю свой компилятор, чтобы не зависеть от этих инструкций. Я начал думать, что байт-код JVM прост и дзэн, и мальчик, насколько я учусь! –