--- a/jdk/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java Mon Jul 04 13:00:15 2016 +0900
+++ b/jdk/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java Mon Jul 04 10:08:18 2016 +0200
@@ -25,22 +25,30 @@
package java.lang.invoke;
-import java.io.*;
-import java.util.*;
-import java.lang.reflect.Modifier;
-
-import jdk.internal.org.objectweb.asm.*;
-
-import static java.lang.invoke.LambdaForm.*;
-import static java.lang.invoke.LambdaForm.BasicType.*;
-import static java.lang.invoke.MethodHandleStatics.*;
-import static java.lang.invoke.MethodHandleNatives.Constants.*;
-
+import jdk.internal.org.objectweb.asm.ClassWriter;
+import jdk.internal.org.objectweb.asm.Label;
+import jdk.internal.org.objectweb.asm.MethodVisitor;
+import jdk.internal.org.objectweb.asm.Opcodes;
+import jdk.internal.org.objectweb.asm.Type;
import sun.invoke.util.VerifyAccess;
import sun.invoke.util.VerifyType;
import sun.invoke.util.Wrapper;
import sun.reflect.misc.ReflectUtil;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import static java.lang.invoke.LambdaForm.*;
+import static java.lang.invoke.LambdaForm.BasicType.*;
+import static java.lang.invoke.MethodHandleNatives.Constants.*;
+import static java.lang.invoke.MethodHandleStatics.*;
+
/**
* Code generation backend for LambdaForm.
* <p>
@@ -75,8 +83,8 @@
private final MethodType invokerType;
/** Info about local variables in compiled lambda form */
- private final int[] localsMap; // index
- private final Class<?>[] localClasses; // type
+ private int[] localsMap; // index
+ private Class<?>[] localClasses; // type
/** ASM bytecode generation. */
private ClassWriter cw;
@@ -101,8 +109,7 @@
this.lambdaForm = lambdaForm;
this.invokerName = invokerName;
this.invokerType = invokerType;
- this.localsMap = new int[localsMapSize+1];
- // last entry of localsMap is count of allocated local slots
+ this.localsMap = new int[localsMapSize+1]; // last entry of localsMap is count of allocated local slots
this.localClasses = new Class<?>[localsMapSize+1];
}
@@ -131,7 +138,6 @@
}
}
-
/** instance counters for dumped classes */
private static final HashMap<String,Integer> DUMP_CLASS_FILES_COUNTERS;
/** debugging flag for saving generated class files */
@@ -177,7 +183,6 @@
}
});
}
-
}
private static String makeDumpableClassName(String className) {
@@ -280,14 +285,11 @@
private static MemberName resolveInvokerMember(Class<?> invokerClass, String name, MethodType type) {
MemberName member = new MemberName(invokerClass, name, type, REF_invokeStatic);
- //System.out.println("resolveInvokerMember => "+member);
- //for (Method m : invokerClass.getDeclaredMethods()) System.out.println(" "+m);
try {
member = MEMBERNAME_FACTORY.resolveOrFail(REF_invokeStatic, member, HOST_CLASS, ReflectiveOperationException.class);
} catch (ReflectiveOperationException e) {
throw newInternalError(e);
}
- //System.out.println("resolveInvokerMember => "+member);
return member;
}
@@ -541,11 +543,12 @@
Name writeBack = null; // local to write back result
if (arg instanceof Name) {
Name n = (Name) arg;
- if (assertStaticType(cls, n))
- return; // this cast was already performed
if (lambdaForm.useCount(n) > 1) {
// This guy gets used more than once.
writeBack = n;
+ if (assertStaticType(cls, n)) {
+ return; // this cast was already performed
+ }
}
}
if (isStaticallyNameable(cls)) {
@@ -679,19 +682,29 @@
MethodHandleImpl.Intrinsic intr = name.function.intrinsicName();
switch (intr) {
case SELECT_ALTERNATIVE:
- assert isSelectAlternative(i);
+ assert lambdaForm.isSelectAlternative(i);
if (PROFILE_GWT) {
assert(name.arguments[0] instanceof Name &&
- nameRefersTo((Name)name.arguments[0], MethodHandleImpl.class, "profileBoolean"));
+ ((Name)name.arguments[0]).refersTo(MethodHandleImpl.class, "profileBoolean"));
mv.visitAnnotation(INJECTEDPROFILE_SIG, true);
}
onStack = emitSelectAlternative(name, lambdaForm.names[i+1]);
i++; // skip MH.invokeBasic of the selectAlternative result
continue;
case GUARD_WITH_CATCH:
- assert isGuardWithCatch(i);
+ assert lambdaForm.isGuardWithCatch(i);
onStack = emitGuardWithCatch(i);
- i = i+2; // Jump to the end of GWC idiom
+ i += 2; // jump to the end of GWC idiom
+ continue;
+ case TRY_FINALLY:
+ assert lambdaForm.isTryFinally(i);
+ onStack = emitTryFinally(i);
+ i += 2; // jump to the end of the TF idiom
+ continue;
+ case LOOP:
+ assert lambdaForm.isLoop(i);
+ onStack = emitLoop(i);
+ i += 2; // jump to the end of the LOOP idiom
continue;
case NEW_ARRAY:
Class<?> rtype = name.function.methodType().returnType();
@@ -763,7 +776,7 @@
* Emit an invoke for the given name.
*/
void emitInvoke(Name name) {
- assert(!isLinkerMethodInvoke(name)); // should use the static path for these
+ assert(!name.isLinkerMethodInvoke()); // should use the static path for these
if (true) {
// push receiver
MethodHandle target = name.function.resolvedHandle();
@@ -952,84 +965,6 @@
}
/**
- * Check if MemberName is a call to a method named {@code name} in class {@code declaredClass}.
- */
- private boolean memberRefersTo(MemberName member, Class<?> declaringClass, String name) {
- return member != null &&
- member.getDeclaringClass() == declaringClass &&
- member.getName().equals(name);
- }
- private boolean nameRefersTo(Name name, Class<?> declaringClass, String methodName) {
- return name.function != null &&
- memberRefersTo(name.function.member(), declaringClass, methodName);
- }
-
- /**
- * Check if MemberName is a call to MethodHandle.invokeBasic.
- */
- private boolean isInvokeBasic(Name name) {
- if (name.function == null)
- return false;
- if (name.arguments.length < 1)
- return false; // must have MH argument
- MemberName member = name.function.member();
- return memberRefersTo(member, MethodHandle.class, "invokeBasic") &&
- !member.isPublic() && !member.isStatic();
- }
-
- /**
- * Check if MemberName is a call to MethodHandle.linkToStatic, etc.
- */
- private boolean isLinkerMethodInvoke(Name name) {
- if (name.function == null)
- return false;
- if (name.arguments.length < 1)
- return false; // must have MH argument
- MemberName member = name.function.member();
- return member != null &&
- member.getDeclaringClass() == MethodHandle.class &&
- !member.isPublic() && member.isStatic() &&
- member.getName().startsWith("linkTo");
- }
-
- /**
- * Check if i-th name is a call to MethodHandleImpl.selectAlternative.
- */
- private boolean isSelectAlternative(int pos) {
- // selectAlternative idiom:
- // t_{n}:L=MethodHandleImpl.selectAlternative(...)
- // t_{n+1}:?=MethodHandle.invokeBasic(t_{n}, ...)
- if (pos+1 >= lambdaForm.names.length) return false;
- Name name0 = lambdaForm.names[pos];
- Name name1 = lambdaForm.names[pos+1];
- return nameRefersTo(name0, MethodHandleImpl.class, "selectAlternative") &&
- isInvokeBasic(name1) &&
- name1.lastUseIndex(name0) == 0 && // t_{n+1}:?=MethodHandle.invokeBasic(t_{n}, ...)
- lambdaForm.lastUseIndex(name0) == pos+1; // t_{n} is local: used only in t_{n+1}
- }
-
- /**
- * Check if i-th name is a start of GuardWithCatch idiom.
- */
- private boolean isGuardWithCatch(int pos) {
- // GuardWithCatch idiom:
- // t_{n}:L=MethodHandle.invokeBasic(...)
- // t_{n+1}:L=MethodHandleImpl.guardWithCatch(*, *, *, t_{n});
- // t_{n+2}:?=MethodHandle.invokeBasic(t_{n+1})
- if (pos+2 >= lambdaForm.names.length) return false;
- Name name0 = lambdaForm.names[pos];
- Name name1 = lambdaForm.names[pos+1];
- Name name2 = lambdaForm.names[pos+2];
- return nameRefersTo(name1, MethodHandleImpl.class, "guardWithCatch") &&
- isInvokeBasic(name0) &&
- isInvokeBasic(name2) &&
- name1.lastUseIndex(name0) == 3 && // t_{n+1}:L=MethodHandleImpl.guardWithCatch(*, *, *, t_{n});
- lambdaForm.lastUseIndex(name0) == pos+1 && // t_{n} is local: used only in t_{n+1}
- name2.lastUseIndex(name1) == 1 && // t_{n+2}:?=MethodHandle.invokeBasic(t_{n+1})
- lambdaForm.lastUseIndex(name1) == pos+2; // t_{n+1} is local: used only in t_{n+2}
- }
-
- /**
* Emit bytecode for the selectAlternative idiom.
*
* The pattern looks like (Cf. MethodHandleImpl.makeGuardWithTest):
@@ -1155,6 +1090,329 @@
return result;
}
+ /**
+ * Emit bytecode for the tryFinally idiom.
+ * <p>
+ * The pattern looks like (Cf. MethodHandleImpl.makeTryFinally):
+ * <blockquote><pre>{@code
+ * // a0: BMH
+ * // a1: target, a2: cleanup
+ * // a3: box, a4: unbox
+ * // a5 (and following): arguments
+ * tryFinally=Lambda(a0:L,a1:L,a2:L,a3:L,a4:L,a5:L)=>{
+ * t6:L=MethodHandle.invokeBasic(a3:L,a5:L); // box the arguments into an Object[]
+ * t7:L=MethodHandleImpl.tryFinally(a1:L,a2:L,t6:L); // call the tryFinally executor
+ * t8:L=MethodHandle.invokeBasic(a4:L,t7:L);t8:L} // unbox the result; return the result
+ * }</pre></blockquote>
+ * <p>
+ * It is compiled into bytecode equivalent to the following code:
+ * <blockquote><pre>{@code
+ * Throwable t;
+ * Object r;
+ * try {
+ * r = a1.invokeBasic(a5);
+ * } catch (Throwable thrown) {
+ * t = thrown;
+ * throw t;
+ * } finally {
+ * r = a2.invokeBasic(t, r, a5);
+ * }
+ * return r;
+ * }</pre></blockquote>
+ * <p>
+ * Specifically, the bytecode will have the following form (the stack effects are given for the beginnings of
+ * blocks, and for the situations after executing the given instruction - the code will have a slightly different
+ * shape if the return type is {@code void}):
+ * <blockquote><pre>{@code
+ * TRY: (--)
+ * load target (-- target)
+ * load args (-- args... target)
+ * INVOKEVIRTUAL MethodHandle.invokeBasic (depends)
+ * FINALLY_NORMAL: (-- r)
+ * load cleanup (-- cleanup r)
+ * SWAP (-- r cleanup)
+ * ACONST_NULL (-- t r cleanup)
+ * SWAP (-- r t cleanup)
+ * load args (-- args... r t cleanup)
+ * INVOKEVIRTUAL MethodHandle.invokeBasic (-- r)
+ * GOTO DONE
+ * CATCH: (-- t)
+ * DUP (-- t t)
+ * FINALLY_EXCEPTIONAL: (-- t t)
+ * load cleanup (-- cleanup t t)
+ * SWAP (-- t cleanup t)
+ * load default for r (-- r t cleanup t)
+ * load args (-- args... r t cleanup t)
+ * INVOKEVIRTUAL MethodHandle.invokeBasic (-- r t)
+ * POP (-- t)
+ * ATHROW
+ * DONE: (-- r)
+ * }</pre></blockquote>
+ */
+ private Name emitTryFinally(int pos) {
+ Name args = lambdaForm.names[pos];
+ Name invoker = lambdaForm.names[pos+1];
+ Name result = lambdaForm.names[pos+2];
+
+ Label lFrom = new Label();
+ Label lTo = new Label();
+ Label lCatch = new Label();
+ Label lDone = new Label();
+
+ Class<?> returnType = result.function.resolvedHandle().type().returnType();
+ boolean isNonVoid = returnType != void.class;
+ MethodType type = args.function.resolvedHandle().type()
+ .dropParameterTypes(0,1)
+ .changeReturnType(returnType);
+ MethodType cleanupType = type.insertParameterTypes(0, Throwable.class);
+ if (isNonVoid) {
+ cleanupType = cleanupType.insertParameterTypes(1, returnType);
+ }
+ String cleanupDesc = cleanupType.basicType().toMethodDescriptorString();
+
+ // exception handler table
+ mv.visitTryCatchBlock(lFrom, lTo, lCatch, "java/lang/Throwable");
+
+ // TRY:
+ mv.visitLabel(lFrom);
+ emitPushArgument(invoker, 0); // load target
+ emitPushArguments(args, 1); // load args (skip 0: method handle)
+ mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", type.basicType().toMethodDescriptorString(), false);
+ mv.visitLabel(lTo);
+
+ // FINALLY_NORMAL:
+ emitPushArgument(invoker, 1); // load cleanup
+ if (isNonVoid) {
+ mv.visitInsn(Opcodes.SWAP);
+ }
+ mv.visitInsn(Opcodes.ACONST_NULL);
+ if (isNonVoid) {
+ mv.visitInsn(Opcodes.SWAP);
+ }
+ emitPushArguments(args, 1); // load args (skip 0: method handle)
+ mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", cleanupDesc, false);
+ mv.visitJumpInsn(Opcodes.GOTO, lDone);
+
+ // CATCH:
+ mv.visitLabel(lCatch);
+ mv.visitInsn(Opcodes.DUP);
+
+ // FINALLY_EXCEPTIONAL:
+ emitPushArgument(invoker, 1); // load cleanup
+ mv.visitInsn(Opcodes.SWAP);
+ if (isNonVoid) {
+ emitZero(BasicType.basicType(returnType)); // load default for result
+ }
+ emitPushArguments(args, 1); // load args (skip 0: method handle)
+ mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", cleanupDesc, false);
+ if (isNonVoid) {
+ mv.visitInsn(Opcodes.POP);
+ }
+ mv.visitInsn(Opcodes.ATHROW);
+
+ // DONE:
+ mv.visitLabel(lDone);
+
+ return result;
+ }
+
+ /**
+ * Emit bytecode for the loop idiom.
+ * <p>
+ * The pattern looks like (Cf. MethodHandleImpl.loop):
+ * <blockquote><pre>{@code
+ * // a0: BMH
+ * // a1: inits, a2: steps, a3: preds, a4: finis
+ * // a5: box, a6: unbox
+ * // a7 (and following): arguments
+ * loop=Lambda(a0:L,a1:L,a2:L,a3:L,a4:L,a5:L,a6:L,a7:L)=>{
+ * t8:L=MethodHandle.invokeBasic(a5:L,a7:L); // box the arguments into an Object[]
+ * t9:L=MethodHandleImpl.loop(bt:L,a1:L,a2:L,a3:L,a4:L,t8:L); // call the loop executor (with supplied types in bt)
+ * t10:L=MethodHandle.invokeBasic(a6:L,t9:L);t10:L} // unbox the result; return the result
+ * }</pre></blockquote>
+ * <p>
+ * It is compiled into bytecode equivalent to the code seen in {@link MethodHandleImpl#loop(BasicType[],
+ * MethodHandle[], MethodHandle[], MethodHandle[], MethodHandle[], Object...)}, with the difference that no arrays
+ * will be used for local state storage. Instead, the local state will be mapped to actual stack slots.
+ * <p>
+ * Bytecode generation applies an unrolling scheme to enable better bytecode generation regarding local state type
+ * handling. The generated bytecode will have the following form ({@code void} types are ignored for convenience).
+ * Assume there are {@code C} clauses in the loop.
+ * <blockquote><pre>{@code
+ * INIT: (INIT_SEQ for clause 1)
+ * ...
+ * (INIT_SEQ for clause C)
+ * LOOP: (LOOP_SEQ for clause 1)
+ * ...
+ * (LOOP_SEQ for clause C)
+ * GOTO LOOP
+ * DONE: ...
+ * }</pre></blockquote>
+ * <p>
+ * The {@code INIT_SEQ_x} sequence for clause {@code x} (with {@code x} ranging from {@code 0} to {@code C-1}) has
+ * the following shape. Assume slot {@code vx} is used to hold the state for clause {@code x}.
+ * <blockquote><pre>{@code
+ * INIT_SEQ_x: ALOAD inits
+ * CHECKCAST MethodHandle[]
+ * ICONST x
+ * AALOAD // load the init handle for clause x
+ * load args
+ * INVOKEVIRTUAL MethodHandle.invokeBasic
+ * store vx
+ * }</pre></blockquote>
+ * <p>
+ * The {@code LOOP_SEQ_x} sequence for clause {@code x} (with {@code x} ranging from {@code 0} to {@code C-1}) has
+ * the following shape. Again, assume slot {@code vx} is used to hold the state for clause {@code x}.
+ * <blockquote><pre>{@code
+ * LOOP_SEQ_x: ALOAD steps
+ * CHECKCAST MethodHandle[]
+ * ICONST x
+ * AALOAD // load the step handle for clause x
+ * load locals
+ * load args
+ * INVOKEVIRTUAL MethodHandle.invokeBasic
+ * store vx
+ * ALOAD preds
+ * CHECKCAST MethodHandle[]
+ * ICONST x
+ * AALOAD // load the pred handle for clause x
+ * load locals
+ * load args
+ * INVOKEVIRTUAL MethodHandle.invokeBasic
+ * IFNE LOOP_SEQ_x+1 // predicate returned false -> jump to next clause
+ * ALOAD finis
+ * CHECKCAST MethodHandle[]
+ * ICONST x
+ * AALOAD // load the fini handle for clause x
+ * load locals
+ * load args
+ * INVOKEVIRTUAL MethodHandle.invokeBasic
+ * GOTO DONE // jump beyond end of clauses to return from loop
+ * }</pre></blockquote>
+ */
+ private Name emitLoop(int pos) {
+ Name args = lambdaForm.names[pos];
+ Name invoker = lambdaForm.names[pos+1];
+ Name result = lambdaForm.names[pos+2];
+
+ // extract clause and loop-local state types
+ // find the type info in the loop invocation
+ BasicType[] loopClauseTypes = (BasicType[]) invoker.arguments[0];
+ Class<?>[] loopLocalStateTypes = Stream.of(loopClauseTypes).
+ filter(bt -> bt != BasicType.V_TYPE).map(BasicType::basicTypeClass).toArray(Class<?>[]::new);
+
+ final int firstLoopStateIndex = extendLocalsMap(loopLocalStateTypes);
+
+ Class<?> returnType = result.function.resolvedHandle().type().returnType();
+ MethodType loopType = args.function.resolvedHandle().type()
+ .dropParameterTypes(0,1)
+ .changeReturnType(returnType);
+ MethodType loopHandleType = loopType.insertParameterTypes(0, loopLocalStateTypes);
+ MethodType predType = loopHandleType.changeReturnType(boolean.class);
+ MethodType finiType = loopHandleType;
+
+ final int nClauses = loopClauseTypes.length;
+
+ // indices to invoker arguments to load method handle arrays
+ final int inits = 1;
+ final int steps = 2;
+ final int preds = 3;
+ final int finis = 4;
+
+ Label lLoop = new Label();
+ Label lDone = new Label();
+ Label lNext;
+
+ // INIT:
+ for (int c = 0, state = 0; c < nClauses; ++c) {
+ MethodType cInitType = loopType.changeReturnType(loopClauseTypes[c].basicTypeClass());
+ emitLoopHandleInvoke(invoker, inits, c, args, false, cInitType, loopLocalStateTypes, firstLoopStateIndex);
+ if (cInitType.returnType() != void.class) {
+ emitStoreInsn(BasicType.basicType(cInitType.returnType()), firstLoopStateIndex + state);
+ ++state;
+ }
+ }
+
+ // LOOP:
+ mv.visitLabel(lLoop);
+
+ for (int c = 0, state = 0; c < nClauses; ++c) {
+ lNext = new Label();
+
+ MethodType stepType = loopHandleType.changeReturnType(loopClauseTypes[c].basicTypeClass());
+ boolean isVoid = stepType.returnType() == void.class;
+
+ // invoke loop step
+ emitLoopHandleInvoke(invoker, steps, c, args, true, stepType, loopLocalStateTypes, firstLoopStateIndex);
+ if (!isVoid) {
+ emitStoreInsn(BasicType.basicType(stepType.returnType()), firstLoopStateIndex + state);
+ ++state;
+ }
+
+ // invoke loop predicate
+ emitLoopHandleInvoke(invoker, preds, c, args, true, predType, loopLocalStateTypes, firstLoopStateIndex);
+ mv.visitJumpInsn(Opcodes.IFNE, lNext);
+
+ // invoke fini
+ emitLoopHandleInvoke(invoker, finis, c, args, true, finiType, loopLocalStateTypes, firstLoopStateIndex);
+ mv.visitJumpInsn(Opcodes.GOTO, lDone);
+
+ // this is the beginning of the next loop clause
+ mv.visitLabel(lNext);
+ }
+
+ mv.visitJumpInsn(Opcodes.GOTO, lLoop);
+
+ // DONE:
+ mv.visitLabel(lDone);
+
+ return result;
+ }
+
+ private int extendLocalsMap(Class<?>[] types) {
+ int firstSlot = localsMap.length - 1;
+ localsMap = Arrays.copyOf(localsMap, localsMap.length + types.length);
+ localClasses = Arrays.copyOf(localClasses, localClasses.length + types.length);
+ System.arraycopy(types, 0, localClasses, firstSlot, types.length);
+ int index = localsMap[firstSlot - 1] + 1;
+ int lastSlots = 0;
+ for (int i = 0; i < types.length; ++i) {
+ localsMap[firstSlot + i] = index;
+ lastSlots = BasicType.basicType(localClasses[firstSlot + i]).basicTypeSlots();
+ index += lastSlots;
+ }
+ localsMap[localsMap.length - 1] = index - lastSlots;
+ return firstSlot;
+ }
+
+ private void emitLoopHandleInvoke(Name holder, int handles, int clause, Name args, boolean pushLocalState,
+ MethodType type, Class<?>[] loopLocalStateTypes, int firstLoopStateSlot) {
+ // load handle for clause
+ emitPushArgument(holder, handles);
+ emitIconstInsn(clause);
+ mv.visitInsn(Opcodes.AALOAD);
+ // load loop state (preceding the other arguments)
+ if (pushLocalState) {
+ for (int s = 0; s < loopLocalStateTypes.length; ++s) {
+ emitLoadInsn(BasicType.basicType(loopLocalStateTypes[s]), firstLoopStateSlot + s);
+ }
+ }
+ // load loop args (skip 0: method handle)
+ emitPushArguments(args, 1);
+ mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", type.toMethodDescriptorString(), false);
+ }
+
+ private void emitZero(BasicType type) {
+ switch (type) {
+ case I_TYPE: mv.visitInsn(Opcodes.ICONST_0); break;
+ case J_TYPE: mv.visitInsn(Opcodes.LCONST_0); break;
+ case F_TYPE: mv.visitInsn(Opcodes.FCONST_0); break;
+ case D_TYPE: mv.visitInsn(Opcodes.DCONST_0); break;
+ case L_TYPE: mv.visitInsn(Opcodes.ACONST_NULL); break;
+ default: throw new InternalError("unknown type: " + type);
+ }
+ }
+
private void emitPushArguments(Name args) {
emitPushArguments(args, 0);
}