COMPUTE_FRAMES 生成代码中的 ASM 和堆栈框架映射问题
COMPUTE_FRAMES issue with ASM and stackframe maps in generated code
我正在为我在编译器中用作示例的编译器编写代码生成器class我正在教学。我们使用 ASM 5.0.3 生成 JVM 代码。我能够评估最直接的表达式和语句,其中一些调用运行时方法,就好了。对于示例参考语言,我们只有布尔值和整数,以及一些带有类型推断的简单控制结构。在生成的 class 中,所有 "programs" 都被编译为静态 main 方法。这是我使用 ASM 的第一年。我们之前用的是jasmine.
我在堆栈图框架方面遇到了问题。这是我正在编译的一个示例程序,它会出现问题:
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 字节码浏览器中查看 class,我会得到:
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。我错过了什么?我花了很多时间试图让这个正确,并且可以想象我的学生一定会变得多么沮丧。我开始后悔从茉莉花的转变。
问题是您使用的指令 ifeq
不正确。 ifeq
将 一个参数 与零进行比较,并相应地进行分支。您将两个值压入堆栈,因为您似乎将其与 if_icmpeq
混淆,后者将测试 两个操作数 是否相等。因此,在每个条件块之后,一个悬空的 int
保留在堆栈中,因此块之前的代码与它之后的代码具有不同的堆栈深度,这意味着您不能在它上面分支,因为分支是不允许的,如果源和目标有不同的堆栈深度(这正是 VerifierError
告诉你的,你可以看到每个显式帧如何在堆栈上多一个 Integer
)。
因此您可以将 ifeq
指令更改为 if_icmpeq
以反映您的初衷,但保留 ifeq
指令并删除零的推动会更有效常数。
请注意,所有三个指令都进行相同的 ifeq
测试似乎是另一个错误。我想,您想将 < 0
和 > 0
代码编译为相应的 iflt
和 ifgt
指令。再次注意 iflt
/ifgt
和 ificmplt
/ificmpgt
指令之间的区别……
我正在为我在编译器中用作示例的编译器编写代码生成器class我正在教学。我们使用 ASM 5.0.3 生成 JVM 代码。我能够评估最直接的表达式和语句,其中一些调用运行时方法,就好了。对于示例参考语言,我们只有布尔值和整数,以及一些带有类型推断的简单控制结构。在生成的 class 中,所有 "programs" 都被编译为静态 main 方法。这是我使用 ASM 的第一年。我们之前用的是jasmine.
我在堆栈图框架方面遇到了问题。这是我正在编译的一个示例程序,它会出现问题:
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 字节码浏览器中查看 class,我会得到:
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。我错过了什么?我花了很多时间试图让这个正确,并且可以想象我的学生一定会变得多么沮丧。我开始后悔从茉莉花的转变。
问题是您使用的指令 ifeq
不正确。 ifeq
将 一个参数 与零进行比较,并相应地进行分支。您将两个值压入堆栈,因为您似乎将其与 if_icmpeq
混淆,后者将测试 两个操作数 是否相等。因此,在每个条件块之后,一个悬空的 int
保留在堆栈中,因此块之前的代码与它之后的代码具有不同的堆栈深度,这意味着您不能在它上面分支,因为分支是不允许的,如果源和目标有不同的堆栈深度(这正是 VerifierError
告诉你的,你可以看到每个显式帧如何在堆栈上多一个 Integer
)。
因此您可以将 ifeq
指令更改为 if_icmpeq
以反映您的初衷,但保留 ifeq
指令并删除零的推动会更有效常数。
请注意,所有三个指令都进行相同的 ifeq
测试似乎是另一个错误。我想,您想将 < 0
和 > 0
代码编译为相应的 iflt
和 ifgt
指令。再次注意 iflt
/ifgt
和 ificmplt
/ificmpgt
指令之间的区别……