your programing

인라인 메서드 본문에서 invokevirtual에 대한 예상치 못한 지침 및 매개 변수

lovepro 2020. 12. 31. 23:09
반응형

인라인 메서드 본문에서 invokevirtual에 대한 예상치 못한 지침 및 매개 변수


http://asm.ow2.org/current/asm-transformations.pdf 의 "3.2.6 Inline Method"에있는 샘플 코드를 따라 MethodNode를 호출 사이트에 인라인했습니다.

내 문제는 인라인 후 생성 된 바이트 코드에 예기치 않은 지침이 표시된다는 것입니다 (이 바이트 코드는 내 코드와 일치하지 않습니다).이 문제 ifeq는 인라인 메서드 본문 뒤에 있고 스택의 변수가 xLoad에 의해로드되는 경우에만 존재합니다 .

여전히 문제의 근본 원인을 찾지 못했습니다. 이제 최소한의 코드로 재현하기 위해 불필요한 코드를 모두 제거하기 시작했습니다. 좋은 제안이있는 사람은 누구나 환영합니다.

내 기존 창립 중 하나는 다음과 같습니다. 문제는 Frame과 관련이 없습니다. 왜냐하면 Configuration for ClassRewiter가 COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS있고 Configuration for 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);
            }
        }
     ......
}

여기서 발견 은 MethodVisitor가 여기에서 새로 생성 된 _visitors.add(new AnalyzerAdapter..);에서 주석 처리하면 생성 된 클래스가 정확 TransformationChain하다는 것입니다. 메서드의 일부 요소는 상태가있는 것으로 보이며, 이는 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;

When it is first visited by AnalyzerAdapter::visitJmpAdadpter, two ints, e.g., 10 and 11, are inserted at the begin of the array. Then in the next iteration ``MethodCallInliner::visitJmpInsn`, another two new ints are added at position 2 and 3. Now the array content is:

[10, 11, 16, 17, 0, 0] in which the pair (10,11) is for AnalyzerAdapter and the pair (16,17) is for Method MethodCallInliner.

But what puzzles me here is: the ASM should be able to distinct different pairs for the right MethodVisitor when generating the bytcode class (or block, stack frame calculation whatever)?

The code can be accessed by https://github.com/xushijie/InlineMethod/tree/typeinference


The problem is caused when the label (the classreader reads from a class file) is visited by a MethodVisitor pipeline. The label has a field int [] srcAndRefPositions. Two of its consecutive positions (cfr. the end of my original post) are updated once the label is accessed by a MethodVisitor. In my case, the label in the ifeq label holds 2 MethodVisitors. It seems the incorrect position in the srcAndRefPositions is used when generating the class file (using the last MethodVisitor).

I did not investigate the root cause. Instead, my solution was to clone the label and then use the new label when it is visited by a MethodVisitor.

ReferenceURL : https://stackoverflow.com/questions/31998248/method-invocation-instruction-invokevirtual-invokestatic-is-substituted-by-som

반응형