内联方法体中 invokevirtual 的意外指令和参数
unexpected instructions and parameters for invokevirtual in the inlined method body
我按照http://asm.ow2.org/current/asm-transformations.pdf中“3.2.6内联方法”中的示例代码将MethodNode内联到调用站点。
我的问题是在内联后生成的字节码中显示了一些意外的指令(这些字节码与我的代码不一致),只有当 ifeq
在内联方法体之后并且堆栈上的变量由 xLoad 加载。
我还没有找到问题的根本原因。现在我开始删除所有不必要的代码,旨在用最少的代码重现它。欢迎大家提出好的建议。
这是我现有的基础之一:问题与Frame无关,因为当ClassRewiter配置为COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS
和ClassReader配置为ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES
[=29时问题仍然存在=]
为了简化问题,被调用者的正文是:
public invokeExact(Ljava/lang/String;)Z
ICONST_0
IRETURN
来电者是:
public String invokeExact(String a, String b){
boolean flag = _guard.invokeExact(a);
if(flag)
{
return a;
}
return b;
}
。调用者在MethodWriter上对应的字节码操作轨迹为:
public java.lang.String invokeExact(java.lang.String, java.lang.String)
....
4: aload_1
5: astore_3
6: astore 4
8: iconst_0
visitJumpInsn goto L1029004533
//visitmax() empty implementation.
//visitEnd() Empty implementation.
visitlabel L1029004533 // This label is newly created once inlining starts, but is visited until the end of inlining as the target of all xReturn instructions in the Callee's method body.
visitVarInsn istore 5
visitVarInsn iload 5
visitJumpInsn ifeq L980604133
visitVarInsn aload 1
visitInsn areturn
visitLabel L980604133
visitVarInsn aload 2
visitInsn areturn
最后生成的class文件为:
public java.lang.String invokeExact(java.lang.String, java.lang.String);
stack=2, locals=6, args_size=3
0: aload_0
1: getfield #17 // Field _guard:Ltest/code/jit/asm/simple/MHGuard;
4: aload_1
5: astore_3
6: astore 4
8: iconst_0
**9: goto 9
12: fconst_0
13: iconst_2**
14: iload 5
16: ifeq 21
19: aload_1
20: areturn
21: aload_2
22: areturn
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 12
locals = [ class test/code/jit/asm/simple/GWTSample, class java/lang/String, class java/lang/String, class java/lang/String, class test/code/jit/asm/simple/MHGuard ]
stack = [ int ]
frame_type = 252 /* append */
offset_delta = 8
locals = [ int ]
其中#9、#12 和#13 是错误的。
我的部分代码是(周末继续精简我的代码):
public class MethodCallInliner extends LocalVariablesSorter {
protected MethodContext _context;
private IPlugin _plugin;
public MethodCallInliner(int access, String desc, MethodContext context){
// context.getRawMV() return a Class MethodWriter.
super(Opcodes.ASM5, access, desc, context.getRawMV());
_context = context;
//_fieldVisitor = new FieldManipulationVisitor(mv, context);
_plugin = NameMappingService.get().getPlugin();
//removed some unncessary codes..
}
@Override
public void visitMethodInsn(int opcode, String owner, String name,
String desc, boolean itf) {
if(opcode != Opcodes.INVOKEVIRTUAL){
mv.visitMethodInsn(opcode, owner, name, desc, itf);
return;
}
MethodNode mn = _plugin.map(owner, name, desc, _context, this);
if(mn == null){
mv.visitMethodInsn(opcode, owner, name, desc, itf);
return;
}
//ASMUtil.debug(mn); //to double confirm the mn content is correct.
performInline(ASMUtil.isStaticMethod(mn)?Opcodes.INVOKESTATIC:Opcodes.INVOKEVIRTUAL, owner, desc, mn);
_plugin.postProcess(mn, this, _context);
}
protected void performInline(int opcode, String owner, String desc, MethodNode mn){
Remapper remapper = Mapper.getMapper(_context, _context.getReceiverFieldName());
mn.instructions.resetLabels();
Label end = new Label();
System.out.println("++"+end.toString());
_context.beginInline();
mn.accept(new InliningAdapter(this,
opcode == Opcodes.INVOKESTATIC ? Opcodes.ACC_STATIC : 0, desc,
remapper, end, _context));
_context.endInline();
super.visitLabel(end);
}
public void visitJumpInsn(int opcode, Label label) {
super.visitJumpInsn(opcode, label);
}
@Override
public void visitVarInsn(final int opcode, final int var){
super.visitVarInsn(opcode, var);;
}
...
}
[新发现]
我想我现在离问题更近了。
- 内联访问者
MethodCallInliner
应该是正确的,因为使用相同 classes 对该访问者的另一次独立测试成功。
- 问题在于如何构建 MethodVisitor 链。 a) 我只想要访问方法说明的一次通行证。 2)
MethodCallInliner
被安排在链的末尾。在它之前,更多的访问者被插入到推理类型信息中,这些信息可能在 MethodCallInliner
中的方法内联期间使用。
我的链构建器是:
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
.....
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
return new TransformationChain(Opcodes.ASM5, access, name, desc, signature, mv, _context);
//return new MethodCallInliner(access, desc, context); //This is OK.
}
public class TransformationChain extends BaseMethodTransform {
public TransformationChain(int api, int access, String name, String desc, String signature, MethodVisitor mv, ClassContext classContext) {
super(api, mv, classContext.getClassName(), name, desc);
....
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS);
_visitors.add(new AnalyzerAdapter(Opcodes.ASM5, owner, access, name,desc, cw.visitMethod(access, name, desc, owner, null)){
@Override
public void visitJumpInsn(final int opcode, final Label label){
super.visitJumpInsn(opcode, label);
}
});
MethodNode node = new MethodNode(access, name, desc, signature, null);
_visitors.add(node);
//cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS);
//MethodNode node = context.getClassContext().getMethodNode(name, desc);
//_visitors.add(new TypeInferencer(Opcodes.ASM5, cw.visitMethod(access, name, desc, null, null), node, context));
_visitors.add(name.equals(Constants.CONSTRUCTOR)?new ConstructorMerge(access, desc, context):
new MethodCallInliner(access, desc, context));
}
}
abstract class BaseMethodTransform extends MethodVisitor {
protected final List<MethodVisitor> _visitors = new LinkedList<MethodVisitor>();
public BaseMethodTransform(int api, MethodVisitor mv, String className, String methodName, String methodDesc) {
super(api, mv);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name,
String desc, boolean itf) {
for (MethodVisitor mv : _visitors) {
mv.visitMethodInsn(opcode, owner, name, desc, itf);
}
}
@Override
public void visitIntInsn(int opcode, int operand) {
for (MethodVisitor mv : _visitors) {
mv.visitIntInsn(opcode, operand);
}
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
for (MethodVisitor mv : _visitors) {
if (mv!= _visitors.get(_visitors.size()-1) || mv instanceof TraceMethodVisitor) {
continue;
}
mv.visitMaxs(maxStack, maxLocals);
}
}
@Override
public void visitJumpInsn(final int opcode, final Label label) {
for (MethodVisitor mv : _visitors) {
mv.visitJumpInsn(opcode, label);
}
}
......
}
我这里的发现是如果我在TransformationChain
中注释掉_visitors.add(new AnalyzerAdapter..);
生成的class是正确的,其中的MethodVisitor是在这里新建的。 貌似方法的某些元素是有状态的,有可能被MethodWriters修改(虽然都是独立的),之前的修改对后面的访问者有影响。
我也注意到是标签:
/**
* Informations about forward references. Each forward reference is
* described by two consecutive integers in this array: the first one is the
* position of the first byte of the bytecode instruction that contains the
* forward reference, while the second is the position of the first byte of
* the forward reference itself. In fact the sign of the first integer
* indicates if this reference uses 2 or 4 bytes, and its absolute value
* gives the position of the bytecode instruction. This array is also used
* as a bitset to store the subroutines to which a basic block belongs. This
* information is needed in {@linked MethodWriter#visitMaxs}, after all
* forward references have been resolved. Hence the same array can be used
* for both purposes without problems.
*/
private int[] srcAndRefPositions;
当AnalyzerAdapter::visitJmpAdadpter第一次访问它时,两个整数,例如10和11,被插入到数组的开头。然后在下一次迭代``MethodCallInliner::visitJmpInsn`中,在位置2和3添加另外两个新的int。现在数组内容是:
[10, 11, 16, 17, 0, 0]
in which the pair (10,11) is for AnalyzerAdapter and the pair (16,17) is for Method MethodCallInliner
.
但令我困惑的是:ASM 应该能够在生成字节码 class(或块、堆栈帧计算等)时区分正确的 MethodVisitor 的不同对?
代码可以通过https://github.com/xushijie/InlineMethod/tree/typeinference
访问
问题是在 MethodVisitor
管道访问标签(classreader 从 class 文件读取)时引起的。该标签有一个字段 int [] srcAndRefPositions
。一旦标签被 MethodVisitor 访问,它的两个连续位置(参见我原来的 post 的结尾)就会更新。在我的例子中,ifeq label
中的标签包含 2 个 MethodVisitors。生成 class 文件(使用最后一个 MethodVisitor)时,似乎使用了 srcAndRefPositions
中的错误位置。
我没有调查根本原因。相反,我的解决方案是克隆标签,然后在 MethodVisitor 访问时使用新标签。
我按照http://asm.ow2.org/current/asm-transformations.pdf中“3.2.6内联方法”中的示例代码将MethodNode内联到调用站点。
我的问题是在内联后生成的字节码中显示了一些意外的指令(这些字节码与我的代码不一致),只有当 ifeq
在内联方法体之后并且堆栈上的变量由 xLoad 加载。
我还没有找到问题的根本原因。现在我开始删除所有不必要的代码,旨在用最少的代码重现它。欢迎大家提出好的建议。
这是我现有的基础之一:问题与Frame无关,因为当ClassRewiter配置为COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS
和ClassReader配置为ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES
[=29时问题仍然存在=]
为了简化问题,被调用者的正文是:
public invokeExact(Ljava/lang/String;)Z
ICONST_0
IRETURN
来电者是:
public String invokeExact(String a, String b){
boolean flag = _guard.invokeExact(a);
if(flag)
{
return a;
}
return b;
}
。调用者在MethodWriter上对应的字节码操作轨迹为:
public java.lang.String invokeExact(java.lang.String, java.lang.String)
....
4: aload_1
5: astore_3
6: astore 4
8: iconst_0
visitJumpInsn goto L1029004533
//visitmax() empty implementation.
//visitEnd() Empty implementation.
visitlabel L1029004533 // This label is newly created once inlining starts, but is visited until the end of inlining as the target of all xReturn instructions in the Callee's method body.
visitVarInsn istore 5
visitVarInsn iload 5
visitJumpInsn ifeq L980604133
visitVarInsn aload 1
visitInsn areturn
visitLabel L980604133
visitVarInsn aload 2
visitInsn areturn
最后生成的class文件为:
public java.lang.String invokeExact(java.lang.String, java.lang.String);
stack=2, locals=6, args_size=3
0: aload_0
1: getfield #17 // Field _guard:Ltest/code/jit/asm/simple/MHGuard;
4: aload_1
5: astore_3
6: astore 4
8: iconst_0
**9: goto 9
12: fconst_0
13: iconst_2**
14: iload 5
16: ifeq 21
19: aload_1
20: areturn
21: aload_2
22: areturn
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 12
locals = [ class test/code/jit/asm/simple/GWTSample, class java/lang/String, class java/lang/String, class java/lang/String, class test/code/jit/asm/simple/MHGuard ]
stack = [ int ]
frame_type = 252 /* append */
offset_delta = 8
locals = [ int ]
其中#9、#12 和#13 是错误的。
我的部分代码是(周末继续精简我的代码):
public class MethodCallInliner extends LocalVariablesSorter {
protected MethodContext _context;
private IPlugin _plugin;
public MethodCallInliner(int access, String desc, MethodContext context){
// context.getRawMV() return a Class MethodWriter.
super(Opcodes.ASM5, access, desc, context.getRawMV());
_context = context;
//_fieldVisitor = new FieldManipulationVisitor(mv, context);
_plugin = NameMappingService.get().getPlugin();
//removed some unncessary codes..
}
@Override
public void visitMethodInsn(int opcode, String owner, String name,
String desc, boolean itf) {
if(opcode != Opcodes.INVOKEVIRTUAL){
mv.visitMethodInsn(opcode, owner, name, desc, itf);
return;
}
MethodNode mn = _plugin.map(owner, name, desc, _context, this);
if(mn == null){
mv.visitMethodInsn(opcode, owner, name, desc, itf);
return;
}
//ASMUtil.debug(mn); //to double confirm the mn content is correct.
performInline(ASMUtil.isStaticMethod(mn)?Opcodes.INVOKESTATIC:Opcodes.INVOKEVIRTUAL, owner, desc, mn);
_plugin.postProcess(mn, this, _context);
}
protected void performInline(int opcode, String owner, String desc, MethodNode mn){
Remapper remapper = Mapper.getMapper(_context, _context.getReceiverFieldName());
mn.instructions.resetLabels();
Label end = new Label();
System.out.println("++"+end.toString());
_context.beginInline();
mn.accept(new InliningAdapter(this,
opcode == Opcodes.INVOKESTATIC ? Opcodes.ACC_STATIC : 0, desc,
remapper, end, _context));
_context.endInline();
super.visitLabel(end);
}
public void visitJumpInsn(int opcode, Label label) {
super.visitJumpInsn(opcode, label);
}
@Override
public void visitVarInsn(final int opcode, final int var){
super.visitVarInsn(opcode, var);;
}
...
}
[新发现]
我想我现在离问题更近了。
- 内联访问者
MethodCallInliner
应该是正确的,因为使用相同 classes 对该访问者的另一次独立测试成功。 - 问题在于如何构建 MethodVisitor 链。 a) 我只想要访问方法说明的一次通行证。 2)
MethodCallInliner
被安排在链的末尾。在它之前,更多的访问者被插入到推理类型信息中,这些信息可能在MethodCallInliner
中的方法内联期间使用。
我的链构建器是:
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
.....
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
return new TransformationChain(Opcodes.ASM5, access, name, desc, signature, mv, _context);
//return new MethodCallInliner(access, desc, context); //This is OK.
}
public class TransformationChain extends BaseMethodTransform {
public TransformationChain(int api, int access, String name, String desc, String signature, MethodVisitor mv, ClassContext classContext) {
super(api, mv, classContext.getClassName(), name, desc);
....
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS);
_visitors.add(new AnalyzerAdapter(Opcodes.ASM5, owner, access, name,desc, cw.visitMethod(access, name, desc, owner, null)){
@Override
public void visitJumpInsn(final int opcode, final Label label){
super.visitJumpInsn(opcode, label);
}
});
MethodNode node = new MethodNode(access, name, desc, signature, null);
_visitors.add(node);
//cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS);
//MethodNode node = context.getClassContext().getMethodNode(name, desc);
//_visitors.add(new TypeInferencer(Opcodes.ASM5, cw.visitMethod(access, name, desc, null, null), node, context));
_visitors.add(name.equals(Constants.CONSTRUCTOR)?new ConstructorMerge(access, desc, context):
new MethodCallInliner(access, desc, context));
}
}
abstract class BaseMethodTransform extends MethodVisitor {
protected final List<MethodVisitor> _visitors = new LinkedList<MethodVisitor>();
public BaseMethodTransform(int api, MethodVisitor mv, String className, String methodName, String methodDesc) {
super(api, mv);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name,
String desc, boolean itf) {
for (MethodVisitor mv : _visitors) {
mv.visitMethodInsn(opcode, owner, name, desc, itf);
}
}
@Override
public void visitIntInsn(int opcode, int operand) {
for (MethodVisitor mv : _visitors) {
mv.visitIntInsn(opcode, operand);
}
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
for (MethodVisitor mv : _visitors) {
if (mv!= _visitors.get(_visitors.size()-1) || mv instanceof TraceMethodVisitor) {
continue;
}
mv.visitMaxs(maxStack, maxLocals);
}
}
@Override
public void visitJumpInsn(final int opcode, final Label label) {
for (MethodVisitor mv : _visitors) {
mv.visitJumpInsn(opcode, label);
}
}
......
}
我这里的发现是如果我在TransformationChain
中注释掉_visitors.add(new AnalyzerAdapter..);
生成的class是正确的,其中的MethodVisitor是在这里新建的。 貌似方法的某些元素是有状态的,有可能被MethodWriters修改(虽然都是独立的),之前的修改对后面的访问者有影响。
我也注意到是标签:
/**
* Informations about forward references. Each forward reference is
* described by two consecutive integers in this array: the first one is the
* position of the first byte of the bytecode instruction that contains the
* forward reference, while the second is the position of the first byte of
* the forward reference itself. In fact the sign of the first integer
* indicates if this reference uses 2 or 4 bytes, and its absolute value
* gives the position of the bytecode instruction. This array is also used
* as a bitset to store the subroutines to which a basic block belongs. This
* information is needed in {@linked MethodWriter#visitMaxs}, after all
* forward references have been resolved. Hence the same array can be used
* for both purposes without problems.
*/
private int[] srcAndRefPositions;
当AnalyzerAdapter::visitJmpAdadpter第一次访问它时,两个整数,例如10和11,被插入到数组的开头。然后在下一次迭代``MethodCallInliner::visitJmpInsn`中,在位置2和3添加另外两个新的int。现在数组内容是:
[10, 11, 16, 17, 0, 0] in which the pair (10,11) is for AnalyzerAdapter and the pair (16,17) is for Method
MethodCallInliner
.
但令我困惑的是:ASM 应该能够在生成字节码 class(或块、堆栈帧计算等)时区分正确的 MethodVisitor 的不同对?
代码可以通过https://github.com/xushijie/InlineMethod/tree/typeinference
访问问题是在 MethodVisitor
管道访问标签(classreader 从 class 文件读取)时引起的。该标签有一个字段 int [] srcAndRefPositions
。一旦标签被 MethodVisitor 访问,它的两个连续位置(参见我原来的 post 的结尾)就会更新。在我的例子中,ifeq label
中的标签包含 2 个 MethodVisitors。生成 class 文件(使用最后一个 MethodVisitor)时,似乎使用了 srcAndRefPositions
中的错误位置。
我没有调查根本原因。相反,我的解决方案是克隆标签,然后在 MethodVisitor 访问时使用新标签。