Я пишу генератор кода для компилятора, который я использую в качестве примера в классе компилятора, который я преподаю. Мы используем 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. Что мне не хватает? Я потратил много времени, пытаясь сделать это правильно, и могу себе представить, как расстраиваются мои ученики. Я начинаю сожалеть о переходе с жасмина.