I have been investigating this error for a whole three days, but still no progress. I hope I can get some tips from here.
What I am trying to do is to inline a MethodNode into a MethodHandle Call Site (#5, #17, and #30) with ASM library. For simplification, the MethodHandle at #5 references a static method static Functions.isFooString(String)boolen
.
At the call site, the instruction before inlining is like
//Before
stack=3, locals=3, args_size=3
0: aload_0
1: getfield #15 // Field guard:Ljava/lang/invoke/MethodHandle;
4: aload_1
5: invokevirtual #29 // Method java/lang/invoke/MethodHandle.invokeExact:(Ljava/lang/String;)Z
8: ifeq 24
11: aload_0
12: getfield #17 // Field trueTarget:Ljava/lang/invoke/MethodHandle;
15: aload_1
16: aload_2
17: invokevirtual #31 // Method java/lang/invoke/MethodHandle.invokeExact:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
20: checkcast #33 // class java/lang/String
23: areturn
24: aload_0
25: getfield #19 // Field falseTarget:Ljava/lang/invoke/MethodHandle;
28: aload_1
29: aload_2
30: invokevirtual #31 // Method java/lang/invoke/MethodHandle.invokeExact:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
33: checkcast #33 // class java/lang/String
36: areturn
StackMapTable: number_of_entries = 1
frame_type = 24 /* same */
Exceptions:
throws java.lang.Throwable
The inlining rule is that copy inline's body into the call site after storing the arguments to local variables. After inlining, the instruction list is:
//After
Classfile /C:/temp/DYNGuardWithTestHandle1439587569404.class
Last modified Aug 14, 2015; size 913 bytes
MD5 checksum 055a99d52cb622a7e86c59de79347f3e
public class DYNGuardWithTestHandle1439587569404 extends java.lang.invoke.BaseTemplate
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Utf8 DYNGuardWithTestHandle1439587569404
#2 = Class #1 // DYNGuardWithTestHandle1439587569404
#3 = Utf8 java/lang/invoke/BaseTemplate
#4 = Class #3 // java/lang/invoke/BaseTemplate
#5 = Utf8 guard
#6 = Utf8 Ljava/lang/invoke/MethodHandle;
#7 = Utf8 trueTarget
#8 = Utf8 falseTarget
#9 = Utf8 <init>
#10 = Utf8 (Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;)V
#11 = Utf8 ()V
#12 = NameAndType #9:#11 // "<init>":()V
#13 = Methodref #4.#12 // java/lang/invoke/BaseTemplate."<init>":()V
#14 = NameAndType #5:#6 // guard:Ljava/lang/invoke/MethodHandle;
#15 = Fieldref #2.#14 // DYNGuardWithTestHandle1439587569404.guard:Ljava/lang/invoke/MethodHandle;
#16 = NameAndType #7:#6 // trueTarget:Ljava/lang/invoke/MethodHandle;
#17 = Fieldref #2.#16 // DYNGuardWithTestHandle1439587569404.trueTarget:Ljava/lang/invoke/MethodHandle;
#18 = NameAndType #8:#6 // falseTarget:Ljava/lang/invoke/MethodHandle;
#19 = Fieldref #2.#18 // DYNGuardWithTestHandle1439587569404.falseTarget:Ljava/lang/invoke/MethodHandle;
#20 = Utf8 eval
#21 = Utf8 invokeExact
#22 = Utf8 (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#23 = Utf8 java/lang/Throwable
#24 = Class #23 // java/lang/Throwable
#25 = Utf8 test/code/jit/asm/methodhandle/Functions
#26 = Class #25 // test/code/jit/asm/methodhandle/Functions
#27 = Utf8 isFooString
#28 = Utf8 (Ljava/lang/String;)Z
#29 = NameAndType #27:#28 // isFooString:(Ljava/lang/String;)Z
#30 = Methodref #26.#29 // test/code/jit/asm/methodhandle/Functions.isFooString:(Ljava/lang/String;)Z
#31 = Utf8 printTrueTarget
#32 = NameAndType #31:#22 // printTrueTarget:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#33 = Methodref #26.#32 // test/code/jit/asm/methodhandle/Functions.printTrueTarget:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#34 = Utf8 java/lang/String
#35 = Class #34 // java/lang/String
#36 = Utf8 java/lang/Object
#37 = Class #36 // java/lang/Object
#38 = Utf8 printFalseTarget
#39 = NameAndType #38:#22 // printFalseTarget:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#40 = Methodref #26.#39 // test/code/jit/asm/methodhandle/Functions.printFalseTarget:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#41 = Utf8 Code
#42 = Utf8 StackMapTable
#43 = Utf8 Exceptions
{
final java.lang.invoke.MethodHandle guard;
descriptor: Ljava/lang/invoke/MethodHandle;
flags: ACC_FINAL
final java.lang.invoke.MethodHandle trueTarget;
descriptor: Ljava/lang/invoke/MethodHandle;
flags: ACC_FINAL
final java.lang.invoke.MethodHandle falseTarget;
descriptor: Ljava/lang/invoke/MethodHandle;
flags: ACC_FINAL
public DYNGuardWithTestHandle1439587569404(java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle);
descriptor: (Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;)V
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=4
0: aload_0
1: invokespecial #13 // Method java/lang/invoke/BaseTemplate."<init>":()V
4: aload_0
5: aload_1
6: putfield #15 // Field guard:Ljava/lang/invoke/MethodHandle;
9: aload_0
10: aload_2
11: putfield #17 // Field trueTarget:Ljava/lang/invoke/MethodHandle;
14: aload_0
15: aload_3
16: putfield #19 // Field falseTarget:Ljava/lang/invoke/MethodHandle;
19: return
public void eval();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: return
public java.lang.String invokeExact(java.lang.String, java.lang.String) throws java.lang.Throwable;
descriptor: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=3, locals=8, args_size=3
0: aload_0
1: getfield #15 // Field guard:Ljava/lang/invoke/MethodHandle;
4: aload_1
5: astore_3
6: aload_3
7: invokestatic #0 // #0
10: fload_2
11: nop
12: iconst_0
13: swap
14: pop
15: ifeq 44
18: aload_0
19: getfield #17 // Field trueTarget:Ljava/lang/invoke/MethodHandle;
22: aload_1
23: aload_2
24: astore 4
26: astore 5
28: aload 5
30: aload 4
32: invokestatic #33 // Method test/code/jit/asm/methodhandle/Functions.printTrueTarget:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
35: goto 38
38: swap
39: pop
40: checkcast #35 // class java/lang/String
43: areturn
44: aload_0
45: getfield #19 // Field falseTarget:Ljava/lang/invoke/MethodHandle;
48: aload_1
49: aload_2
50: astore 6
52: astore 7
54: aload 7
56: aload 6
58: invokestatic #40 // Method test/code/jit/asm/methodhandle/Functions.printFalseTarget:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
61: goto 64
64: swap
65: pop
66: checkcast #35 // class java/lang/String
69: areturn
StackMapTable: number_of_entries = 1
frame_type = 254 /* append */
offset_delta = 44
locals = [ class java/lang/Object, class java/lang/Object, class java/lang/Object ]
Exceptions:
throws java.lang.Throwable
}
Clearly, the #5 (before)in the original instruction is replaced by WRONG instructions #7--#12 (after), which should not exist here. Instead, there should only be one instruction, here:
invokestatic Method test/code/jit/asm/methodhandle/Functions.isFooString:(Ljava/lang/String;)Z;
Worse, the #7 of the invokeExact(after) is completely illegal which caused the JVM crash down. The other two instructions #17 and #30 (before) are correctly substituted.
It is clear from the generated bytecodes, but this is different from the behavior defined by my code. The way to build the MethodNode is:
MethodNode buildMethod(...){
...
access=ACC_PUBLIC+ACC_STATIC;
String owner = Type.getType(definingClass).getInternalName();
String desc = type.toMethodDescriptorString();
MethodNode methodNode = new MethodNode(Opcodes.ASM5, access, mhName, type.toString(), null, null);
if(name.equals("isFooString")){
methodNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
System.out.println("owner="+owner+" name="+name+" desc="+desc);
//owner=test/code/jit/asm/methodhandle/Functions name=isFooString desc=(Ljava/lang/String;)Z
methodNode.instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, owner, name, desc, false));
methodNode.instructions.add(new InsnNode(Opcodes.IRETURN));
return methodNode;
}
AbstractInsnNode insn = new MethodInsnNode(originIsFindVirtual?Opcodes.INVOKEVIRTUAL: Opcodes.INVOKESTATIC, owner, name, desc, false);
Type[] args = Type.getArgumentTypes(desc);
for(Type arg : args){
AbstractInsnNode node = new VarInsnNode(arg.getOpcode(Opcodes.ILOAD), start);
start += arg.getSize();
methodNode.instructions.add(node);
}
methodNode.instructions.add(insn);
int Return = Type.getReturnType(desc).getOpcode(Opcodes.IRETURN);
methodNode.instructions.add(new InsnNode(Return));
return methodNode;
}
And InliningAdapter:
//The full version can be accessed by https://github.com/xushijie/InlineMethod/blob/typeinference/src/main/java/code/jit/asm/core/InliningAdapter.java
public class InliningAdapter extends RemappingMethodAdapter{
/** The main task in the Constructor is to store pushed parameters of the MethodNode to a new local variables and store the remaining arguments of the stack to another temporary stacks in case of exceptions in the MethodNode */
public InliningAdapter(LocalVariablesSorter lvsMV, int acc, String desc,
Remapper remapper, Label end, MethodContext context) {
super(acc, desc, lvsMV, remapper);
lvs = lvsMV;
mv = context.getRawMV();
this.end = end;
_context = context;
List<Type> types = _context.getOperandStack();
int offset = ((acc & Opcodes.ACC_STATIC) != 0 ? 0 : 1);
Type[] args = Type.getArgumentTypes(desc);
for (int i = args.length - 1; i >= 0; i--) {
super.visitVarInsn(args[i].getOpcode(Opcodes.ISTORE), i + offset);
}
int poped = args.length;
if (offset > 0) {
poped++;
super.visitVarInsn(Opcodes.ASTORE, 0);
}
int left = types.size() - poped - 1;
while (left > 0) {
// NON-parameters in the stack => pop up too and restore back after
// complete.
int variable = newLocal(types.get(left));
int opcode = types.get(left).getOpcode(Opcodes.ISTORE);
__callerstacks.add(0, new StackEle(types.get(left), variable)); // |-->TOP
mv.visitVarInsn(opcode, variable);
left--;
}
}
@Override
public void visitMethodInsn(final int opcode, final String owner,
final String name, final String desc, final boolean itf) {
System.out.println("[Callee: ] invokeVirtual "+ owner +" "+name+" "+ desc);
_context.getRawMV().visitMethodInsn(opcode, owner, name, desc, itf);
//_context.getRawMV() is a MethodWriter. and the value of the (owner, name, desc) is /.//Functions, isFooString, (String;)Z, which is correct.
}
@Override
public void visitVarInsn(final int opcode, final int var) {
//I confirm this method is not invoked after visitMethodInsn.
super.visitVarInsn(opcode, var + firstLocal);
}
}
For each invokevirtual instruction (#5, #17. #30) in the original bytecodes, it is like:
mn = buildMethodNode(...);
mn.accept(new InliningAdapter(this,
opcode == Opcodes.INVOKESTATIC ? Opcodes.ACC_STATIC : 0, desc, _context)); //mn is my constructed MethodNode previsouly, which contains one method invocation.
To resolve the issue( the parameters for the first visited invokestatic
is missing and some illegal instructions are added). I debug my code (Sorry I cannot post all of it here because there are too many files), and it monitors all parameters that are written to the MethodWriter in thevisitMethodInsn
of the InliningAdapter
. It proves that all these values (Functions, isFooString, (String;)Z) are correct, which are the same as my expectation.
So my questions are:
- Is there something wrong in my MethodNode builder?
What kinds of reasons can result to this issue? There are also other similar cases, where the first
invokevirtual
instructions are wrongly substituted by something like, nop, fdouble, and asatore (These substitutions do not exist in my code).Also, I confirm the override method
visitMethodInsn
is not invoked after thevisitMethodInsn
beforeaccept
completes. That means, the wrong instructions, i.e., #10fload
and #12iconst
, are not from the built MethodNode.I made another try that disable the inlining of the first invokevirtual (#5 //Before). The generated class succeeds and can be launched. For me, it looks like the problem is at
isFooString
MethodNode building.