内联方法体

时间:2015-08-17 14:47:56

标签: bytecode java-bytecode-asm bytecode-manipulation

我按照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

时,问题仍然存在

为了简化问题,被调用者的身体是:

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应该是正确的,因为这个访问者的另一个独立测试成功了。
  • 问题在于如何构建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);
            }
        }
     ......
}

我的查找,如果我在_visitors.add(new AnalyzerAdapter..);中注释掉TransformationChain,其中的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]   其中对(10,11)用于AnalyzerAdapter,而对(16,17)用于方法MethodCallInliner

但令我困惑的是:ASM应该能够在生成bytcode类(或块,堆栈帧计算等)时为正确的MethodVisitor区分不同的对吗?

可以通过https://github.com/xushijie/InlineMethod/tree/typeinference

访问代码

1 个答案:

答案 0 :(得分:1)

MethodVisitor管道访问标签(类读取器从类文件中读取)时,会导致问题。标签有一个字段int [] srcAndRefPositions。一旦MethodVisitor访问标签,它的两个连续位置(cfr。我的原始帖子的结尾)将被更新。就我而言,ifeq label中的标签包含2个MethodVisitors。在生成类文件时(使用最后一个MethodVisitor),似乎使用了srcAndRefPositions中的错误位置。

我没有调查根本原因。相反,我的解决方案是克隆标签,然后在MethodVisitor访问时使用新标签。