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.
MethodCallInliner
should be correct as another independent testing of this visitor with the same classes succeeds. 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 the MethodCallInliner
. 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
When the JVM invokes a method, it creates a stack frame of the proper size for that method. Adding a new frame onto the Java stack when a method is invoked is called "pushing" a stack frame; removing a frame when a method returns is called "popping" a stack frame. The Java stack is made up solely of these frames.
invokevirtual retrieves the Java class for objectref, and searches the list of methods defined by that class and then its superclasses, looking for a method called methodname, whose descriptor is descriptor. Once a method has been located, invokevirtual calls the method.
The invokespecial instruction is used to invoke instance initialization methods as well as private methods and methods of a superclass of the current class. In other words, invokespecial is used to call methods without concern for dynamic binding, in order to invoke the particular class' version of a method.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With