Проблема COMPUTE_FRAMES с картами ASM и stackframe в сгенерированном коде

Я пишу генератор кода для компилятора, который я использую в качестве примера в классе компилятора, который я преподаю. Мы используем ASM 5.0.3 для генерации кода JVM. Я могу оценить большинство прямых выражений и операторов, некоторые из которых вызывают методы времени выполнения, просто отлично. Для примера эталонного языка у нас есть только логические значения и целые числа, а также несколько простых управляющих структур с выводом типов. Все «программы» компилируются в статический основной метод результирующего класса. Это первый год, когда я использую ASM. Ранее мы использовали жасмин.

У меня проблемы с кадрами карты стека. Вот пример программы, которую я компилирую, которая покажет проблему:

i <- 5
if
  i < 0 :: j <- 0
  i = 0 :: j <- 1
  i > 0 :: j <- 2
fi

Существует эквивалент программы Java:

int i = 5, j;
if (i < 0) j = 0;
else if (i == 0)j = 1;
else if (i > 0) j = 2;

Если бы я просто написал программу с одной альтернативой, она сгенерировала бы нормально. Но когда у меня их несколько, как в этом случае, я получаю такую ​​трассировку:

java.lang.VerifyError: Inconsistent stackmap frames at branch target 39
Exception Details:
  Location:
    djkcode/Test.main([Ljava/lang/String;)V @12: goto
  Reason:
    Current frame's stack size doesn't match stackmap.
  Current Frame:
    bci: @12
    flags: { }
    locals: { '[Ljava/lang/String;', integer, integer }
    stack: { integer }
  Stackmap Frame:
    bci: @39
    flags: { }
    locals: { '[Ljava/lang/String;', integer }
    stack: { integer, integer, integer }
  Bytecode:
    0x0000000: 120b 3c1b 120c 9900 0912 0c3d a700 1b1b
    0x0000010: 120c 9900 0912 0d3d a700 0f1b 120c 9900
    0x0000020: 0912 0e3d a700 03b1                    
  Stackmap Table:
    full_frame(@15,{Object[#16],Integer},{Integer})
    full_frame(@27,{Object[#16],Integer},{Integer,Integer})
    full_frame(@39,{Object[#16],Integer},{Integer,Integer,Integer})

Вызовы ASM, которые я делаю, можно увидеть в этом выводе отладки:

DBG>     cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
DBG>     cw.visit(V1_8, ACC_PUBLIC+ACC_STATIC, "djkcode/Test", null, "java/lang/Object", null
DBG>        Generate the default constructor..this works in all cases
DBG>     
Start the main method
DBG>     mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null
DBG>     mv.visitCode()
DBG>     mv.visitLdcInsn(5);
DBG>     mv.visitVarInsn(ISTORE, assign.getId().getAddress());
DBG> Enter Alternative
DBG>     Label endLabel = new Label();  // value = L692342133
DBG> Enter Guard
DBG>     Label failLabel = new Label(); // failLabel = L578866604
DBG>     mv.visitVarInsn(ILOAD, 1);
DBG>     mv.visitLdcInsn(0);
DBG>     mv.visitJumpInsn(IFEQ, failLabel);
DBG>     mv.visitLdcInsn(0);
DBG>     mv.visitVarInsn(ISTORE, assign.getId().getAddress());
DBG>     mv.visitJumpInsn(GOTO, guardLabelStack.peek());  // label value = L692342133
DBG>     mv.visitLabel(failLabel);  // failLabel = L578866604
DBG> L578866604:
DBG> Exit Guard
DBG> Enter Guard
DBG>     Label failLabel = new Label(); // failLabel = L1156060786
DBG>     mv.visitVarInsn(ILOAD, 1);
DBG>     mv.visitLdcInsn(0);
DBG>     mv.visitJumpInsn(IFEQ, failLabel);
DBG>     mv.visitLdcInsn(1);
DBG>     mv.visitVarInsn(ISTORE, assign.getId().getAddress());
DBG>     mv.visitJumpInsn(GOTO, guardLabelStack.peek());  // label value = L692342133
DBG>     mv.visitLabel(failLabel);  // failLabel = L1156060786
DBG> L1156060786:
DBG> Exit Guard
DBG> Enter Guard
DBG>     Label failLabel = new Label(); // failLabel = L1612799726
DBG>     mv.visitVarInsn(ILOAD, 1);
DBG>     mv.visitLdcInsn(0);
DBG>     mv.visitJumpInsn(IFEQ, failLabel);
DBG>     mv.visitLdcInsn(2);
DBG>     mv.visitVarInsn(ISTORE, assign.getId().getAddress());
DBG>     mv.visitJumpInsn(GOTO, guardLabelStack.peek());  // label value = L692342133
DBG>     mv.visitLabel(failLabel);  // failLabel = L1612799726
DBG> L1612799726:
DBG> Exit Guard
DBG>     mv.visitLabel(endLabel);  // endLabel = L692342133
DBG> L692342133:
DBG> Exit Alternative
DBG>     mv.visitInsn(RETURN)
DBG>     mv.visitMaxs(0, 0);
DBG>     mv.visitEnd();
DBG>     cw.visitEnd();

Если я просматриваю класс в браузере байт-кода Eclips ASM, я получаю следующее:

package asm.djkcode;
...
ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
AnnotationVisitor av0;

cw.visit(52, ACC_PUBLIC + ACC_STATIC, "djkcode/Test", null, "java/lang/Object", null);

...

{
mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
...
mv.visitJumpInsn(GOTO, l1);
mv.visitLabel(l0);
mv.visitFrame(Opcodes.F_FULL, 2, new Object[] {"[Ljava/lang/String;", Opcodes.INTEGER}, 1, new Object[] {Opcodes.INTEGER});
...
mv.visitJumpInsn(GOTO, l1);
mv.visitLabel(l2);
mv.visitFrame(Opcodes.F_FULL, 2, new Object[] {"[Ljava/lang/String;", Opcodes.INTEGER}, 2, new Object[] {Opcodes.INTEGER, Opcodes.INTEGER});
...
mv.visitJumpInsn(GOTO, l1);
mv.visitLabel(l1);
mv.visitFrame(Opcodes.F_FULL, 2, new Object[] {"[Ljava/lang/String;", Opcodes.INTEGER}, 3, new Object[] {Opcodes.INTEGER, Opcodes.INTEGER, Opcodes.INTEGER});
mv.visitInsn(RETURN);
mv.visitMaxs(4, 3);
mv.visitEnd();
}
cw.visitEnd();

Кажется, некоторые операторы visitFrame неверны, но в документации (я использую ASM 5.0.3) говорится, что если вы используете COMPUTE_FRAMES, вам не нужно вызывать visitFrame. Что мне не хватает? Я потратил много времени, пытаясь сделать это правильно, и могу себе представить, как расстраиваются мои ученики. Я начинаю сожалеть о переходе с жасмина.


person Gary Pollice    schedule 25.02.2015    source источник


Ответы (1)


Проблема в том, что вы используете инструкцию ifeq неправильно. ifeq сравнивает один аргумент с нулем и выполняет соответствующее ветвление. Вы помещаете два значения в стек, поскольку вы, кажется, путаете его с if_icmpeq, который будет проверять, равны ли два операнда. Следовательно, после каждого условного блока в стеке остается висячий int, поэтому код перед блоком имеет другую глубину стека, чем код, следующий за ним, что означает, что вы не можете переходить через него, поскольку переходы не разрешены, если источник и цель имеют разную глубину стека (именно это и говорит вам VerifierError, вы можете видеть, как каждый явный кадр имеет еще один Integer в стеке).

Таким образом, вы могли бы изменить свою инструкцию ifeq на if_icmpeq, чтобы отразить свое первоначальное намерение, но было бы более эффективно сохранить инструкцию ifeq и убрать добавление нулевой константы.

Обратите внимание, что еще одной ошибкой является то, что все три инструкции выполняют один и тот же тест ifeq. Я думаю, вы хотите скомпилировать код < 0 и > 0 в соответствии с инструкциями iflt и ifgt. Опять же, обратите внимание на разницу между инструкциями iflt/ifgt и ificmplt/ificmpgt

person Holger    schedule 25.02.2015
comment
Это абсолютно проблема. Вот почему у меня есть парная программа для моих учеников. Сначала я реализовал тест и в итоге добавил реляционный оператор до того, как реализовал процедуру генерации кода реляционного выражения. Это действительно смущает, но большое спасибо. - person Gary Pollice; 26.02.2015