gpt4 book ai didi

bytecode - 内联方法体中 invokevirtual 的意外指令和参数

转载 作者:IT老高 更新时间:2023-10-28 20:24:35 27 4
gpt4 key购买 nike

我按照 http://asm.ow2.org/current/asm-transformations.pdf 中“3.2.6 内联方法”中的示例代码,将 MethodNode 内联到调用站点。

我的问题是内联后生成的字节码中显示了一些意想不到的指令(这些字节码与我的代码不一致),并且仅当 ifeq 在内联方法体之后存在问题并且堆栈上的变量由 xLoad 加载。

我仍然没有找到问题的根本原因。现在我开始删除所有不必要的代码,旨在用最少的代码重现它。欢迎大家有好的建议。

这是我现有的创建之一:问题与框架无关,因为当 ClassRewiter 的配置为 COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS 和 ClassReader 的配置 时问题仍然存在ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES

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

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 应该是正确的,因为使用相同类对该访问者进行的另一个独立测试成功。
  • 问题在于如何构建 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..);,则生成的类是正确的>,这里新建的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 应该能够在生成字节码类(或 block 、堆栈帧计算等)时为正确的 MethodVisitor 区分不同的对?

代码可以通过https://github.com/xushijie/InlineMethod/tree/typeinference访问

最佳答案

该问题是由 MethodVisitor 管道访问标签(类读取器从类文件中读取)引起的。标签有一个字段 int [] srcAndRefPositions。一旦 MethodVisitor 访问标签,它的两个连续位置(参见我原始帖子的结尾)就会更新。在我的例子中,ifeq 标签 中的标签包含 2 个 MethodVisitor。生成类文件时(使用最后一个 MethodVisitor)似乎使用了 srcAndRefPositions 中的不正确位置。

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

关于bytecode - 内联方法体中 invokevirtual 的意外指令和参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31998248/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com