问题
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.
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.
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
And the Caller is:
public String invokeExact(String a, String b){
boolean flag = _guard.invokeExact(a);
if(flag)
{
return a;
}
return b;
}
. 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 ]
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);;
}
...
}
[New Findings]
I think I am much closer to the problem now.
- 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
.
My chain builder is:
@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);
}
}
......
}
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.
I also noticed it is the Label:
/**
* 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
回答1:
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.
来源:https://stackoverflow.com/questions/32053405/unexpected-instructions-and-parameters-for-invokevirtual-in-the-inlined-method-b