src/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java
Index
Unified diffs
Context diffs
Sdiffs
Patch
New
Old
Previous File
Next File
*** old/src/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java Mon Mar 24 20:12:11 2014
--- new/src/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java Mon Mar 24 20:12:10 2014
*** 23,47 ****
--- 23,46 ----
* questions.
*/
package java.lang.invoke;
import sun.invoke.util.VerifyAccess;
import static java.lang.invoke.LambdaForm.*;
import sun.invoke.util.Wrapper;
import java.io.*;
import java.util.*;
+ import java.lang.reflect.Modifier;
import jdk.internal.org.objectweb.asm.*;
! import java.lang.reflect.*;
! 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 static java.lang.invoke.LambdaForm.BasicType.*;
! import sun.invoke.util.ValueConversions;
import sun.invoke.util.VerifyType;
+ import sun.invoke.util.VerifyAccess;
+ import sun.invoke.util.Wrapper;
/**
* Code generation backend for LambdaForm.
* <p>
* @author John Rose, JSR 292 EG
*** 71,89 ****
--- 70,93 ----
private final String sourceFile;
private final LambdaForm lambdaForm;
private final String invokerName;
private final MethodType invokerType;
private final int[] localsMap;
+
+ /** Info about local variables in compiled lambda form */
+ private final int[] localsMap; // index
+ private final BasicType[] localTypes; // basic type
+ private final Class<?>[] localClasses; // type
/** ASM bytecode generation. */
private ClassWriter cw;
private MethodVisitor mv;
private static final MemberName.Factory MEMBERNAME_FACTORY = MemberName.getFactory();
private static final Class<?> HOST_CLASS = LambdaForm.class;
+ /** Main constructor; other constructors delegate to this one. */
private InvokerBytecodeGenerator(LambdaForm lambdaForm, int localsMapSize,
String className, String invokerName, MethodType invokerType) {
if (invokerName.contains(".")) {
int p = invokerName.indexOf(".");
className = invokerName.substring(0, p);
*** 95,124 ****
--- 99,140 ----
this.className = superName + "$" + className;
this.sourceFile = "LambdaForm$" + className;
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.localTypes = new BasicType[localsMapSize+1];
+ this.localClasses = new Class<?>[localsMapSize+1];
}
+ /** For generating LambdaForm interpreter entry points. */
private InvokerBytecodeGenerator(String className, String invokerName, MethodType invokerType) {
this(null, invokerType.parameterCount(),
className, invokerName, invokerType);
// Create an array to map name indexes to locals indexes.
+ localTypes[localTypes.length - 1] = V_TYPE;
for (int i = 0; i < localsMap.length; i++) {
localsMap[i] = invokerType.parameterSlotCount() - invokerType.parameterSlotDepth(i);
+ if (i < invokerType.parameterCount())
+ localTypes[i] = basicType(invokerType.parameterType(i));
}
}
+ /** For generating customized code for a single LambdaForm. */
private InvokerBytecodeGenerator(String className, LambdaForm form, MethodType invokerType) {
this(form, form.names.length,
className, form.debugName, invokerType);
// Create an array to map name indexes to locals indexes.
Name[] names = form.names;
for (int i = 0, index = 0; i < localsMap.length; i++) {
localsMap[i] = index;
! index += names[i].type.basicTypeSlots();
! if (i < names.length) {
+ BasicType type = names[i].type();
+ index += type.basicTypeSlots();
+ localTypes[i] = type;
+ }
}
}
/** instance counters for dumped classes */
*** 145,162 ****
--- 161,178 ----
}
}
static void maybeDump(final String className, final byte[] classFile) {
if (DUMP_CLASS_FILES) {
System.out.println("dump: " + className);
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
try {
String dumpName = className;
//dumpName = dumpName.replace('/', '-');
File dumpFile = new File(DUMP_CLASS_FILES_DIR, dumpName+".class");
+ System.out.println("dump: " + dumpFile);
dumpFile.getParentFile().mkdirs();
FileOutputStream file = new FileOutputStream(dumpFile);
file.write(classFile);
file.close();
return null;
*** 201,211 ****
--- 217,227 ----
int cph = 0; // for counting constant placeholders
String constantPlaceholder(Object arg) {
String cpPlaceholder = "CONSTANT_PLACEHOLDER_" + cph++;
! if (DUMP_CLASS_FILES) cpPlaceholder += " <<" + arg.toString() + ">>"; // debugging aid
! if (DUMP_CLASS_FILES) cpPlaceholder += " <<" + debugString(arg) + ">>"; // debugging aid
if (cpPatches.containsKey(cpPlaceholder)) {
throw new InternalError("observed CP placeholder twice: " + cpPlaceholder);
}
// insert placeholder in CP and remember the patch
int index = cw.newConst((Object) cpPlaceholder); // TODO check if aready in the constant pool
*** 222,231 ****
--- 238,258 ----
res[p.index] = p.value;
}
return res;
}
+ private static String debugString(Object arg) {
+ if (arg instanceof MethodHandle) {
+ MethodHandle mh = (MethodHandle) arg;
+ MemberName member = mh.internalMemberName();
+ if (member != null)
+ return member.toString();
+ return mh.debugString();
+ }
+ return arg.toString();
+ }
+
/**
* Extract the number of constant pool entries from a given class file.
*
* @param classFile the bytes of the class file in question.
* @return the number of entries in the constant pool.
*** 400,409 ****
--- 427,460 ----
}
private void emitAstoreInsn(int index) {
emitStoreInsn(L_TYPE, index);
}
+ private void freeFrameLocal(int oldFrameLocal) {
+ int i = indexForFrameLocal(oldFrameLocal);
+ if (i < 0) return;
+ BasicType type = localTypes[i];
+ int newFrameLocal = makeLocalTemp(type);
+ mv.visitVarInsn(loadInsnOpcode(type), oldFrameLocal);
+ mv.visitVarInsn(storeInsnOpcode(type), newFrameLocal);
+ assert(localsMap[i] == oldFrameLocal);
+ localsMap[i] = newFrameLocal;
+ assert(indexForFrameLocal(oldFrameLocal) < 0);
+ }
+ private int indexForFrameLocal(int frameLocal) {
+ for (int i = 0; i < localsMap.length; i++) {
+ if (localsMap[i] == frameLocal && localTypes[i] != V_TYPE)
+ return i;
+ }
+ return -1;
+ }
+ private int makeLocalTemp(BasicType type) {
+ int frameLocal = localsMap[localsMap.length - 1];
+ localsMap[localsMap.length - 1] = frameLocal + type.basicTypeSlots();
+ return frameLocal;
+ }
+
/**
* Emit a boxing call.
*
* @param wrapper primitive type class to box.
*/
*** 421,465 ****
--- 472,554 ----
*/
private void emitUnboxing(Wrapper wrapper) {
String owner = "java/lang/" + wrapper.wrapperType().getSimpleName();
String name = wrapper.primitiveSimpleName() + "Value";
String desc = "()" + wrapper.basicTypeChar();
! mv.visitTypeInsn(Opcodes.CHECKCAST, owner);
! emitReferenceCast(wrapper.wrapperType(), null);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, desc, false);
}
/**
! * Emit an implicit conversion for an argument which must be of the given pclass.
+ * This is usually a no-op, except when pclass is a subword type or a reference other than Object or an interface.
*
* @param ptype type of value present on stack
* @param pclass type of value required on stack
+ * @param arg compile-time representation of value on stack (Node, constant) or null if none
*/
! private void emitImplicitConversion(BasicType ptype, Class<?> pclass, Object arg) {
assert(basicType(pclass) == ptype); // boxing/unboxing handled by caller
if (pclass == ptype.basicTypeClass() && ptype != L_TYPE)
return; // nothing to do
switch (ptype) {
case L_TYPE:
if (VerifyType.isNullConversion(Object.class, pclass))
+ if (VerifyType.isNullConversion(Object.class, pclass, false)) {
+ if (PROFILE_LEVEL > 0)
+ emitReferenceCast(Object.class, arg);
+ return;
+ }
+ emitReferenceCast(pclass, arg);
+ return;
+ case I_TYPE:
+ if (!VerifyType.isNullConversion(int.class, pclass, false))
+ emitPrimCast(ptype.basicTypeWrapper(), Wrapper.forPrimitiveType(pclass));
return;
if (isStaticallyNameable(pclass)) {
! mv.visitTypeInsn(Opcodes.CHECKCAST, getInternalName(pclass));
+ }
! throw newInternalError("bad implicit conversion: tc="+ptype+": "+pclass);
+ }
+
+ /** Update localClasses type map. Return true if the information is already present. */
+ private boolean assertStaticType(Class<?> cls, Name n) {
+ int local = n.index();
+ Class<?> aclass = localClasses[local];
+ if (aclass != null && (aclass == cls || cls.isAssignableFrom(aclass))) {
+ return true; // type info is already present
+ } else if (aclass == null || aclass.isAssignableFrom(cls)) {
+ localClasses[local] = cls; // type info can be improved
+ }
+ return false;
+ }
+
+ private void emitReferenceCast(Class<?> cls, Object arg) {
+ 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 (isStaticallyNameable(cls)) {
+ String sig = getInternalName(cls);
+ mv.visitTypeInsn(Opcodes.CHECKCAST, sig);
} else {
! mv.visitLdcInsn(constantPlaceholder(pclass));
! mv.visitLdcInsn(constantPlaceholder(cls));
mv.visitTypeInsn(Opcodes.CHECKCAST, CLS);
mv.visitInsn(Opcodes.SWAP);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, MHI, "castReference", CLL_SIG, false);
! if (pclass.isArray())
! if (Object[].class.isAssignableFrom(cls))
mv.visitTypeInsn(Opcodes.CHECKCAST, OBJARY);
+ else if (PROFILE_LEVEL > 0)
+ mv.visitTypeInsn(Opcodes.CHECKCAST, OBJ);
}
return;
case I_TYPE:
if (!VerifyType.isNullConversion(int.class, pclass))
emitPrimCast(ptype.basicTypeWrapper(), Wrapper.forPrimitiveType(pclass));
return;
+ if (writeBack != null) {
+ mv.visitInsn(Opcodes.DUP);
+ emitAstoreInsn(writeBack.index());
}
throw new InternalError("bad implicit conversion: tc="+ptype+": "+pclass);
}
/**
* Emits an actual return instruction conforming to the given return type.
*/
*** 477,486 ****
--- 566,579 ----
}
mv.visitInsn(opcode);
}
private static String getInternalName(Class<?> c) {
+ if (c == Object.class) return OBJ;
+ else if (c == Object[].class) return OBJARY;
+ else if (c == Class.class) return CLS;
+ else if (c == MethodHandle.class) return MH;
assert(VerifyAccess.isTypeVisible(c, Object.class));
return c.getName().replace('.', '/');
}
/**
*** 509,534 ****
--- 602,633 ----
// iterate over the form's names, generating bytecode instructions for each
// start iterating at the first name following the arguments
for (int i = lambdaForm.arity; i < lambdaForm.names.length; i++) {
Name name = lambdaForm.names[i];
MemberName member = name.function.member();
+ Class<?> rtype = name.function.methodType().returnType();
if (isSelectAlternative(i)) {
emitSelectAlternative(name, lambdaForm.names[i + 1]);
i++; // skip MH.invokeBasic of the selectAlternative result
} else if (isGuardWithCatch(i)) {
emitGuardWithCatch(i);
i = i+2; // Jump to the end of GWC idiom
+ } else if (isNewArray(rtype, name)) {
+ emitNewArray(rtype, name);
} else if (isStaticallyInvocable(member)) {
+ if (isLinkerMethodInvoke(name))
+ preserveLinkerMethodTarget(name);
emitStaticInvoke(member, name);
} else {
emitInvoke(name);
}
// Update cached form name's info in case an intrinsic spanning multiple names was encountered.
name = lambdaForm.names[i];
member = name.function.member();
+ rtype = name.function.methodType().returnType();
// store the result from evaluating to the target name in a local if required
// (if this is the last value, i.e., the one that is going to be returned,
// avoid store/load/return and just return)
if (i == lambdaForm.names.length - 1 && i == lambdaForm.result) {
*** 548,571 ****
--- 647,698 ----
final byte[] classFile = cw.toByteArray();
maybeDump(className, classFile);
return classFile;
}
+ /** Certain parts of the JVM expect that, when a call to linkToStatic is made,
+ * the DMH that produced its MN argument is in local #0.
+ * This is often true, since the lambda form of a DMH will start with
+ * the DMH 'l'in arg 0 (local #0) and will end with a call to its linker method.
+ * But if the DMH is transformed, a new lambda form may be created with
+ * the DMH placed elsewhere.
+ */
+ private void preserveLinkerMethodTarget(Name name) {
+ Object lastArg = name.arguments[name.arguments.length - 1];
+ Name targetMember = (Name) lastArg;
+ assert(targetMember.type() == L_TYPE &&
+ !targetMember.isParam() &&
+ isStaticallyInvocable(targetMember.function.member()) &&
+ targetMember.arguments.length == 1);
+ Object targetDMH = DirectMethodHandle.findDirectMethodHandle(targetMember);
+ assert(targetDMH != null);
+ if (!(targetDMH instanceof Name && ((Name)targetDMH).index() == 0)) {
+ // Oops, the DMH is not in a local #0, or is a constant.
+ int DMH_FRAME_INDEX = 0; // hardwired to local #0
+ BasicType DMH_TYPE = L_TYPE;
+ freeFrameLocal(DMH_FRAME_INDEX);
+ // Save it to local #0:
+ emitPushArgument(Object.class, targetDMH);
+ mv.visitVarInsn(storeInsnOpcode(DMH_TYPE), DMH_FRAME_INDEX);
+ }
+ }
+
/**
* Emit an invoke for the given name.
*/
void emitInvoke(Name name) {
+ assert(!isLinkerMethodInvoke(name)); // should use the static path for these
if (true) {
// push receiver
MethodHandle target = name.function.resolvedHandle;
assert(target != null) : name.exprString();
mv.visitLdcInsn(constantPlaceholder(target));
! mv.visitTypeInsn(Opcodes.CHECKCAST, MH);
! emitReferenceCast(MethodHandle.class, target);
} else {
// load receiver
emitAloadInsn(0);
! mv.visitTypeInsn(Opcodes.CHECKCAST, MH);
! emitReferenceCast(MethodHandle.class, null);
mv.visitFieldInsn(Opcodes.GETFIELD, MH, "form", LF_SIG);
mv.visitFieldInsn(Opcodes.GETFIELD, LF, "names", LFN_SIG);
// TODO more to come
}
*** 609,618 ****
--- 736,747 ----
return true;
return false;
}
static boolean isStaticallyNameable(Class<?> cls) {
+ if (cls == Object.class)
+ return true;
while (cls.isArray())
cls = cls.getComponentType();
if (cls.isPrimitive())
return true; // int[].class, for example
// could use VerifyAccess.isClassAccessible but the following is a safe approximation
*** 632,642 ****
--- 761,772 ----
/**
* Emit an invoke for the given name, using the MemberName directly.
*/
void emitStaticInvoke(MemberName member, Name name) {
assert(member.equals(name.function.member()));
! String cname = getInternalName(member.getDeclaringClass());
! Class<?> defc = member.getDeclaringClass();
+ String cname = getInternalName(defc);
String mname = member.getName();
String mtype;
byte refKind = member.getReferenceKind();
if (refKind == REF_invokeSpecial) {
// in order to pass the verifier, we need to convert this to invokevirtual in all cases
*** 662,671 ****
--- 792,849 ----
member.getDeclaringClass().isInterface());
} else {
mtype = MethodType.toFieldDescriptorString(member.getFieldType());
mv.visitFieldInsn(refKindOpcode(refKind), cname, mname, mtype);
}
+ // Issue a type assertion for the result, so we can avoid casts later.
+ if (name.type == L_TYPE) {
+ Class<?> rtype = member.getInvocationType().returnType();
+ assert(!rtype.isPrimitive());
+ if (rtype != Object.class && !rtype.isInterface()) {
+ assertStaticType(rtype, name);
+ }
+ }
+ }
+
+ boolean isNewArray(Class<?> rtype, Name name) {
+ return rtype.isArray() &&
+ isStaticallyNameable(rtype) &&
+ isArrayBuilder(name.function.resolvedHandle) &&
+ name.arguments.length > 0;
+ }
+
+ void emitNewArray(Class<?> rtype, Name name) throws InternalError {
+ Class<?> arrayElementType = rtype.getComponentType();
+ emitIconstInsn(name.arguments.length);
+ int xas;
+ if (!arrayElementType.isPrimitive()) {
+ mv.visitTypeInsn(Opcodes.ANEWARRAY, getInternalName(arrayElementType));
+ xas = Opcodes.AASTORE;
+ } else {
+ int tc;
+ switch (Wrapper.forPrimitiveType(arrayElementType)) {
+ case BOOLEAN: tc = Opcodes.T_BOOLEAN; xas = Opcodes.BASTORE; break;
+ case BYTE: tc = Opcodes.T_BYTE; xas = Opcodes.BASTORE; break;
+ case CHAR: tc = Opcodes.T_CHAR; xas = Opcodes.CASTORE; break;
+ case SHORT: tc = Opcodes.T_SHORT; xas = Opcodes.SASTORE; break;
+ case INT: tc = Opcodes.T_INT; xas = Opcodes.IASTORE; break;
+ case LONG: tc = Opcodes.T_LONG; xas = Opcodes.LASTORE; break;
+ case FLOAT: tc = Opcodes.T_FLOAT; xas = Opcodes.FASTORE; break;
+ case DOUBLE: tc = Opcodes.T_DOUBLE; xas = Opcodes.DASTORE; break;
+ default: throw new InternalError(rtype.getName());
+ }
+ mv.visitIntInsn(Opcodes.NEWARRAY, tc);
+ }
+ // store arguments
+ for (int i = 0; i < name.arguments.length; i++) {
+ mv.visitInsn(Opcodes.DUP);
+ emitIconstInsn(i);
+ emitPushArgument(name, i);
+ mv.visitInsn(xas);
+ }
+ // the array is left on the stack
+ assertStaticType(rtype, name);
}
int refKindOpcode(byte refKind) {
switch (refKind) {
case REF_invokeVirtual: return Opcodes.INVOKEVIRTUAL;
case REF_invokeStatic: return Opcodes.INVOKESTATIC;
*** 677,686 ****
--- 855,887 ----
case REF_putStatic: return Opcodes.PUTSTATIC;
}
throw new InternalError("refKind="+refKind);
}
+ static boolean isArrayBuilder(MethodHandle fn) {
+ if (fn == null)
+ return false;
+ MethodType mtype = fn.type();
+ Class<?> rtype = mtype.returnType();
+ Class<?> arrayElementType = rtype.getComponentType();
+ if (arrayElementType == null)
+ return false;
+ List<Class<?>> ptypes = mtype.parameterList();
+ int size = ptypes.size();
+ if (!ptypes.equals(Collections.nCopies(size, arrayElementType)))
+ return false;
+ // Assume varargsArray caches pointers.
+ if (fn != ValueConversions.varargsArray(rtype, size))
+ return false;
+ return true;
+ }
+
+ static boolean match(MemberName member, MethodHandle fn) {
+ if (member == null || fn == null) return false;
+ return member.equals(fn.internalMemberName());
+ }
+
/**
* 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 &&
*** 704,713 ****
--- 905,929 ----
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(...)
*** 759,774 ****
--- 975,990 ----
Label L_fallback = new Label();
Label L_done = new Label();
// load test result
emitPushArgument(selectAlternativeName, 0);
mv.visitInsn(Opcodes.ICONST_1);
// if_icmpne L_fallback
! mv.visitJumpInsn(Opcodes.IF_ICMPNE, L_fallback);
! mv.visitJumpInsn(Opcodes.IFEQ, L_fallback);
// invoke selectAlternativeName.arguments[1]
+ Class<?>[] preForkClasses = localClasses.clone();
emitPushArgument(selectAlternativeName, 1); // get 2nd argument of selectAlternative
emitAstoreInsn(receiver.index()); // store the MH in the receiver slot
emitInvoke(invokeBasicName);
// goto L_done
*** 776,791 ****
--- 992,1010 ----
// L_fallback:
mv.visitLabel(L_fallback);
// invoke selectAlternativeName.arguments[2]
+ System.arraycopy(preForkClasses, 0, localClasses, 0, preForkClasses.length);
emitPushArgument(selectAlternativeName, 2); // get 3rd argument of selectAlternative
emitAstoreInsn(receiver.index()); // store the MH in the receiver slot
emitInvoke(invokeBasicName);
// L_done:
mv.visitLabel(L_done);
+ // for now do not bother to merge typestate; just reset to the dominator state
+ System.arraycopy(preForkClasses, 0, localClasses, 0, preForkClasses.length);
}
/**
* Emit bytecode for the guardWithCatch idiom.
*
*** 874,892 ****
--- 1093,1111 ----
private void emitPushArgument(Class<?> ptype, Object arg) {
BasicType bptype = basicType(ptype);
if (arg instanceof Name) {
Name n = (Name) arg;
emitLoadInsn(n.type, n.index());
! emitImplicitConversion(n.type, ptype, n);
} else if ((arg == null || arg instanceof String) && bptype == L_TYPE) {
emitConst(arg);
} else {
if (Wrapper.isWrapperType(arg.getClass()) && bptype != L_TYPE) {
emitConst(arg);
} else {
mv.visitLdcInsn(constantPlaceholder(arg));
! emitImplicitConversion(L_TYPE, ptype, arg);
}
}
}
/**
*** 905,918 ****
--- 1124,1137 ----
LambdaForm.Name rn = lambdaForm.names[lambdaForm.result];
// put return value on the stack if it is not already there
if (lambdaForm.result != lambdaForm.names.length - 1 ||
lambdaForm.result < lambdaForm.arity) {
! emitLoadInsn(rn.type, lambdaForm.result);
! emitLoadInsn(rtype, lambdaForm.result);
}
! emitImplicitConversion(rtype, rclass, rn);
// generate actual return statement
emitReturnInsn(rtype);
}
}
src/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java
Index
Unified diffs
Context diffs
Sdiffs
Patch
New
Old
Previous File
Next File