[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.
В вашем первом фрагменте вы прыгаете с 'ifne', но во втором фрагменте кода, с другим идентичным кодом, вы прыгаете с' ifeq'. Это намеренно? 'ifeq' кажется правильным кодом операции для чего вы хотите, так почему же он не в первом фрагменте? –
Спасибо, что заметили, исправили первый фрагмент –
Второй вопрос: откуда вы знаете, что он всегда ветви? Вы один шаг по байт-коду? В противном случае вы, вероятно, наблюдаете косвенным образом - и это также может быть проблемой. –