问题描述
我遵循了,将MethodNode内联到调用站点。
I followed the sample code in the "3.2.6 Inline Method" in the http://asm.ow2.org/current/asm-transformations.pdf, to inline a MethodNode to a call site.
我的问题是内联后,生成的字节码中显示了一些意外的指令(这些字节码与我的代码不一致),并且仅当内联方法主体和 ifeq
之后的内联变量时,问题才存在
My problem is that there are some unexpected instructions shown in the generated bytecode after inlining (these bytecodes are inconsistent to my code), and the problem only exists when an ifeq
is after inlineed method body and the variable on the stack is loaded by xLoad.
我仍然没有找到问题的根本原因,现在我开始删除所有不必要的代码,旨在以最少的成本重现它。
I still have not found the root cause for the issue. Now i am start to remove all unncessary codes, aiming to reproduce it with least code. Anyone has good suggestions are welcome.
这是我现有的创建之一:问题与框架无关,因为当配置Configuration时问题仍然存在ClassRewiter为 COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS
并为ClassReader配置 ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES
Here is one of my existing founding: the problem is not related to the Frame, because the problem is still there when Configuration for ClassRewiter is COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS
and Configuration for ClassReader ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES
为简化此问题,被调用方的身体为:
To simplify the problem, the callee's body is:
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上的相应字节码操作跟踪为:
. The corresponding bytecode manipulation trace of the caller on the MethodWriter is:
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
最后,生成的类文件是:
Finally, the generated class file is:
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错误。
where #9, #12, and #13 are wrong.
部分代码是(我将在周末继续简化代码):
Parts of my code are (I will continue to simple my code on the weekend):
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);;
}
...
}
[新发现]
我认为我现在更接近这个问题了。
I think I am much closer to the problem now.
- 内联访问者
MethodCallInliner
应该是正确的,因为对此访问者的另一项独立测试是同样的课程成功了。 - 问题在于如何构建MethodVisitor链。 a)我只想访问一次方法说明。 2)
MethodCallInliner
被安排在链的末尾。在此之前,更多的访问者被插入到推断类型信息中,这可以在MethodCallInliner
的方法内联过程中使用。
- The inlining visitor
MethodCallInliner
should be correct as another independent testing of this visitor with the same classes succeeds. - The issue is at the way how to build the MethodVisitor chain. a) I want only one pass visiting on the Method instructions. 2) The
MethodCallInliner
is arranged at the end of the chain. Before it, some more visitors are inserted to inference type information, which would possible used during method inlining in theMethodCallInliner
.
我的连锁创建者是:
@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修改(即使它们都是独立的),并且先前的修改会对以后的访问者产生影响。
My Finding here is that the generated class is correct if I comment out _visitors.add(new AnalyzerAdapter..);
in the TransformationChain
, the MethodVisitor of which is newly created here. It seems that some elements of a method have status, which might be modified by MethodWriters (even they are all independent) and the previous modification has impacts on the later visitors.
我也注意到它是标签:
/**
* 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处添加了另外两个新的整数。现在,数组内容为:
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:
但是让我感到困惑的是:ASM在生成bytcode类时应该能够为正确的MethodVisitor区分不同的对(或块,堆栈帧计算等)?
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)?
可以通过
推荐答案
问题是由于标签( (从类文件读取的类读取器)由 MethodVisitor
管道访问。标签的字段为 int [] srcAndRefPositions
。一旦通过MethodVisitor访问标签,将更新其两个连续位置(请参阅原始帖子的末尾)。就我而言, ifeq标签
中的标签包含2个MethodVisitors。似乎在生成类文件(使用最后一个MethodVisitor)时,使用了 srcAndRefPositions
中的错误位置。
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).
我没有调查根本原因。相反,我的解决方案是克隆标签,然后在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.
这篇关于内联方法主体中用于invokevirtual的意外指令和参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!