Gary Pollice
Gary Pollice

Reputation: 55

COMPUTE_FRAMES issue with ASM and stackframe maps in generated code

I'm writing a code generator for a compiler that I'm using as an example in the compiler class I'm teaching. We're using ASM 5.0.3 to generate JVM code. I'm able to evaluate most straight forward expressions and statements, some that call runtime methods, just fine. For the example reference language, we have only boolean and ints, and some simple control structures, with type inference. All "programs" get compiled to a static main method in the resulting class. This is the first year I've used ASM. We previously used jasmine.

I'm having problems with stackmap frames. Here's a sample program that I'm compiling that will exhibit the problem:

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

There is equivalent to a Java program:

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

If I just wrote the program with one alternative, it generates fine. But when I have more than one, as in this case, I get a trace like this:

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})

The ASM calls I make can be seen in this debug output:

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();

If I view the class in the Eclips ASM bytecode browser, I get this:

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();

It seems to have some visitFrame statements that are incorrect, but the documentation (I'm using ASM 5.0.3) says that if you use COMPUTE_FRAMES you don't have to call visitFrame. What am I missing? I've spent a lot of time trying to get this correct and can imagine how frustrated my students must be getting. I'm beginning to rue the switch from jasmine.

Upvotes: 2

Views: 1154

Answers (1)

Holger
Holger

Reputation: 298233

The problem is that you are using the instruction ifeq incorrectly. ifeq compares one argument with zero and will branch accordingly. You are pushing two values onto the stack as you seem to confuse it with if_icmpeq which will test if two operands are equal. Therefore, after each conditional block a dangling int remains on the stack so the code before the block has a different stack depth than the code following it, which implies that you can’t branch over it as branches are not allowed if the source and target have different stack depths (which is exactly what the VerifierError tells you, you can see how each explicit frame has one more Integer on the stack).

So you could change your ifeq instruction to if_icmpeq to reflect your original intention, but it would be more efficient to keep the ifeq instruction and remove the pushing of the zero constant.

Note that it seems to be another mistake that all three instructions do the same ifeq test. I guess, you want to compile your < 0 and > 0 code to the according iflt and ifgt instructions. Again, mind the difference between iflt/ifgt and ificmplt/ificmpgt instructions…

Upvotes: 3

Related Questions