在内联方法体中invokevirtual的意外指令和参数
我遵循http://asm.ow2.org/current/asm-transformations.pdf中的“3.2.6 Inline Method”中的示例代码,将MethodNode内联到一个调用站点。
我的问题是内联后生成的字节码中出现了一些意外的指令(这些字节码与我的代码不一致),并且问题仅在ifeq
在内联方法体之后存在并且xLoad加载堆栈上的variables时才存在。
我还没有find问题的根源。 现在我开始删除所有不明智的代码,旨在用最less的代码重现它。 任何人有好的build议,欢迎。
下面是我现有的创始人之一:问题是不相关的框架,因为问题仍然存在当configurationClassRewiter是COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS
和configurationClassReader ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES
ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES
为了简化问题,被调用者的身体是:
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
最后,生成的类文件是:
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
应该是正确的,因为对同一个类成功访问该访问者的另一个独立testing。 - 问题在于如何构buildMethodVisitor链。 a)我只需要一次访问方法说明。 2)
MethodCallInliner
被安排在链的末尾。 在此之前,一些更多的访问者被插入到推理types信息中,这可能在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); } } ...... }
我在这里find的是,如果我注释掉_visitors.add(new AnalyzerAdapter..);
生成的类是正确的_visitors.add(new AnalyzerAdapter..);
在TransformationChain
,这里新创build了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.现在数组的内容是:
[10,11,16,17,0,0],其中对(10,11)用于AnalyzerAdapter,对(16,17)用于Method
MethodCallInliner
。
但是我在这里困惑的是:当生成bytcode类(或块,堆栈框架计算什么)时,ASM应该能够为正确的MethodVisitor区分不同的对?
代码可以通过https://github.com/xushijie/InlineMethod/tree/typeinference进行访问
问题是由MethodVisitor
pipe道访问标签(类读取器从类文件中读取)时MethodVisitor
。 标签有一个字段int [] srcAndRefPositions
。 一旦MethodVisitor访问了标签,它的两个连续位置(比如我的原始文章的末尾)就会更新。 就我而言, ifeq label
中的ifeq label
有2个MethodVisitors。 在生成类文件时(使用最后一个MethodVisitor),似乎srcAndRefPositions
位置不正确。
我没有调查根本原因。 相反,我的解决scheme是克隆标签,然后在MethodVisitor访问时使用新标签。