< prev index next >
src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java
Print this page
rev 58565 : 8238358: Implementation of JEP 371: Hidden Classes
Reviewed-by: duke
Contributed-by: mandy.chung@oracle.com, lois.foltan@oracle.com, david.holmes@oracle.com, harold.seigel@oracle.com, serguei.spitsyn@oracle.com, alex.buckley@oracle.com, jamsheed.c.m@oracle.com
*** 24,33 ****
--- 24,34 ----
*/
package java.lang.invoke;
import jdk.internal.org.objectweb.asm.ClassWriter;
+ import jdk.internal.org.objectweb.asm.FieldVisitor;
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;
*** 40,56 ****
--- 41,59 ----
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+ import java.util.List;
import java.util.stream.Stream;
import static java.lang.invoke.LambdaForm.BasicType;
import static java.lang.invoke.LambdaForm.BasicType.*;
import static java.lang.invoke.LambdaForm.*;
import static java.lang.invoke.MethodHandleNatives.Constants.*;
import static java.lang.invoke.MethodHandleStatics.*;
+ import static java.lang.invoke.MethodHandles.Lookup.*;
/**
* Code generation backend for LambdaForm.
* <p>
* @author John Rose, JSR 292 EG
*** 65,74 ****
--- 68,79 ----
private static final String OBJ = "java/lang/Object";
private static final String OBJARY = "[Ljava/lang/Object;";
private static final String LOOP_CLAUSES = MHI + "$LoopClauses";
private static final String MHARY2 = "[[L" + MH + ";";
+ private static final String MH_SIG = "L" + MH + ";";
+
private static final String LF_SIG = "L" + LF + ";";
private static final String LFN_SIG = "L" + LFN + ";";
private static final String LL_SIG = "(L" + OBJ + ";)L" + OBJ + ";";
private static final String LLV_SIG = "(L" + OBJ + ";L" + OBJ + ";)V";
*** 90,106 ****
--- 95,121 ----
private Class<?>[] localClasses; // type
/** ASM bytecode generation. */
private ClassWriter cw;
private MethodVisitor mv;
+ private final List<ClassData> classData = new ArrayList<>();
/** Single element internal class name lookup cache. */
private Class<?> lastClass;
private String lastInternalName;
private static final MemberName.Factory MEMBERNAME_FACTORY = MemberName.getFactory();
private static final Class<?> HOST_CLASS = LambdaForm.class;
+ private static final MethodHandles.Lookup LOOKUP = lookup();
+
+ private static MethodHandles.Lookup lookup() {
+ try {
+ return MethodHandles.privateLookupIn(HOST_CLASS, IMPL_LOOKUP);
+ } catch (IllegalAccessException e) {
+ throw newInternalError(e);
+ }
+ }
/** Main constructor; other constructors delegate to this one. */
private InvokerBytecodeGenerator(LambdaForm lambdaForm, int localsMapSize,
String className, String invokerName, MethodType invokerType) {
int p = invokerName.indexOf('.');
*** 219,263 ****
sfx = "0"+sfx;
className += sfx;
return className;
}
! class CpPatch {
! final int index;
final Object value;
! CpPatch(int index, Object value) {
! this.index = index;
this.value = value;
}
public String toString() {
! return "CpPatch/index="+index+",value="+value;
}
}
! private final ArrayList<CpPatch> cpPatches = new ArrayList<>();
!
! private int cph = 0; // for counting constant placeholders
! String constantPlaceholder(Object arg) {
! String cpPlaceholder = "CONSTANT_PLACEHOLDER_" + cph++;
! if (DUMP_CLASS_FILES) cpPlaceholder += " <<" + debugString(arg) + ">>";
! // TODO check if arg is already in the constant pool
! // insert placeholder in CP and remember the patch
! int index = cw.newConst((Object) cpPlaceholder);
! cpPatches.add(new CpPatch(index, arg));
! return cpPlaceholder;
}
! Object[] cpPatches(byte[] classFile) {
! int size = getConstantPoolSize(classFile);
! Object[] res = new Object[size];
! for (CpPatch p : cpPatches) {
! if (p.index >= size)
! throw new InternalError("in cpool["+size+"]: "+p+"\n"+Arrays.toString(Arrays.copyOf(classFile, 20)));
! res[p.index] = p.value;
}
! return res;
}
private static String debugString(Object arg) {
if (arg instanceof MethodHandle) {
MethodHandle mh = (MethodHandle) arg;
--- 234,289 ----
sfx = "0"+sfx;
className += sfx;
return className;
}
! public static class ClassData {
! final String name;
! final String desc;
final Object value;
!
! ClassData(String name, String desc, Object value) {
! this.name = name;
! this.desc = desc;
this.value = value;
}
+
+ public String name() { return name; }
public String toString() {
! return name + ",value="+value;
}
}
! String classData(Object arg) {
! String desc;
! if (arg instanceof Class) {
! desc = "Ljava/lang/Class;";
! } else if (arg instanceof MethodHandle) {
! desc = MH_SIG;
! } else if (arg instanceof LambdaForm) {
! desc = LF_SIG;
! } else {
! desc = "Ljava/lang/Object;";
! }
! Class<?> c = arg.getClass();
! while (c.isArray()) {
! c = c.getComponentType();
! }
! // unique static variable name
! String name = "_DATA_" + c.getSimpleName() + "_" + classData.size();
! ClassData cd = new ClassData(name, desc, arg);
! classData.add(cd);
! return cd.name();
}
! List<Object> classDataValues() {
! Object[] data = new Object[classData.size()];
! for (int i = 0; i < classData.size(); i++) {
! data[i] = classData.get(i).value;
}
! return List.of(data);
}
private static String debugString(Object arg) {
if (arg instanceof MethodHandle) {
MethodHandle mh = (MethodHandle) arg;
*** 286,308 ****
/**
* Extract the MemberName of a newly-defined method.
*/
private MemberName loadMethod(byte[] classFile) {
! Class<?> invokerClass = loadAndInitializeInvokerClass(classFile, cpPatches(classFile));
return resolveInvokerMember(invokerClass, invokerName, invokerType);
}
- /**
- * Define a given class as anonymous class in the runtime system.
- */
- private static Class<?> loadAndInitializeInvokerClass(byte[] classBytes, Object[] patches) {
- Class<?> invokerClass = UNSAFE.defineAnonymousClass(HOST_CLASS, classBytes, patches);
- UNSAFE.ensureClassInitialized(invokerClass); // Make sure the class is initialized; VM might complain.
- return invokerClass;
- }
-
private static MemberName resolveInvokerMember(Class<?> invokerClass, String name, MethodType type) {
MemberName member = new MemberName(invokerClass, name, type, REF_invokeStatic);
try {
member = MEMBERNAME_FACTORY.resolveOrFail(REF_invokeStatic, member, HOST_CLASS, ReflectiveOperationException.class);
} catch (ReflectiveOperationException e) {
--- 312,326 ----
/**
* Extract the MemberName of a newly-defined method.
*/
private MemberName loadMethod(byte[] classFile) {
! Class<?> invokerClass = LOOKUP.makeHiddenClassDefiner(classFile)
! .defineClass(true, classDataValues());
return resolveInvokerMember(invokerClass, invokerName, invokerType);
}
private static MemberName resolveInvokerMember(Class<?> invokerClass, String name, MethodType type) {
MemberName member = new MemberName(invokerClass, name, type, REF_invokeStatic);
try {
member = MEMBERNAME_FACTORY.resolveOrFail(REF_invokeStatic, member, HOST_CLASS, ReflectiveOperationException.class);
} catch (ReflectiveOperationException e) {
*** 314,324 ****
/**
* Set up class file generation.
*/
private ClassWriter classFilePrologue() {
final int NOT_ACC_PUBLIC = 0; // not ACC_PUBLIC
! cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
cw.visit(Opcodes.V1_8, NOT_ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER,
CLASS_PREFIX + className, null, INVOKER_SUPER_NAME, null);
cw.visitSource(SOURCE_PREFIX + className, null);
return cw;
}
--- 332,343 ----
/**
* Set up class file generation.
*/
private ClassWriter classFilePrologue() {
final int NOT_ACC_PUBLIC = 0; // not ACC_PUBLIC
! ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
! setClassWriter(cw);
cw.visit(Opcodes.V1_8, NOT_ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER,
CLASS_PREFIX + className, null, INVOKER_SUPER_NAME, null);
cw.visitSource(SOURCE_PREFIX + className, null);
return cw;
}
*** 334,343 ****
--- 353,404 ----
private void methodEpilogue() {
mv.visitMaxs(0, 0);
mv.visitEnd();
}
+ private String className() {
+ return CLASS_PREFIX + className;
+ }
+
+ private void clinit() {
+ clinit(cw, className(), classData);
+ }
+
+ static void clinit(ClassWriter cw, String className, List<ClassData> classData) {
+ if (classData.isEmpty())
+ return;
+
+ for (ClassData p : classData) {
+ // add the static field
+ FieldVisitor fv = cw.visitField(Opcodes.ACC_STATIC|Opcodes.ACC_FINAL, p.name, p.desc, null, null);
+ fv.visitEnd();
+ }
+
+ MethodVisitor mv = cw.visitMethod(Opcodes.ACC_STATIC, "<clinit>", "()V", null, null);
+ mv.visitCode();
+ // bootstrapping issue if using condy
+ mv.visitLdcInsn(Type.getType("L" + className + ";"));
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/invoke/MethodHandleNatives",
+ "classData", "(Ljava/lang/Class;)Ljava/lang/Object;", false);
+ // we should optimize one single element case that does not need to create a List
+ mv.visitTypeInsn(Opcodes.CHECKCAST, "java/util/List");
+ mv.visitVarInsn(Opcodes.ASTORE, 0);
+ int index = 0;
+ for (ClassData p : classData) {
+ // initialize the static field
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ emitIconstInsn(mv, index++);
+ mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/List",
+ "get", "(I)Ljava/lang/Object;", true);
+ mv.visitTypeInsn(Opcodes.CHECKCAST, p.desc.substring(1, p.desc.length()-1));
+ mv.visitFieldInsn(Opcodes.PUTSTATIC, className, p.name, p.desc);
+ }
+ mv.visitInsn(Opcodes.RETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+
/*
* Low-level emit helpers.
*/
private void emitConst(Object con) {
if (con == null) {
*** 406,415 ****
--- 467,480 ----
// fall through:
mv.visitLdcInsn(con);
}
private void emitIconstInsn(final int cst) {
+ emitIconstInsn(mv, cst);
+ }
+
+ private static void emitIconstInsn(MethodVisitor mv, int cst) {
if (cst >= -1 && cst <= 5) {
mv.visitInsn(Opcodes.ICONST_0 + cst);
} else if (cst >= Byte.MIN_VALUE && cst <= Byte.MAX_VALUE) {
mv.visitIntInsn(Opcodes.BIPUSH, cst);
} else if (cst >= Short.MIN_VALUE && cst <= Short.MAX_VALUE) {
*** 575,586 ****
}
if (isStaticallyNameable(cls)) {
String sig = getInternalName(cls);
mv.visitTypeInsn(Opcodes.CHECKCAST, sig);
} else {
! mv.visitLdcInsn(constantPlaceholder(cls));
! mv.visitTypeInsn(Opcodes.CHECKCAST, CLS);
mv.visitInsn(Opcodes.SWAP);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, CLS, "cast", LL_SIG, false);
if (Object[].class.isAssignableFrom(cls))
mv.visitTypeInsn(Opcodes.CHECKCAST, OBJARY);
else if (PROFILE_LEVEL > 0)
--- 640,650 ----
}
if (isStaticallyNameable(cls)) {
String sig = getInternalName(cls);
mv.visitTypeInsn(Opcodes.CHECKCAST, sig);
} else {
! mv.visitFieldInsn(Opcodes.GETSTATIC, className(), classData(cls), "Ljava/lang/Class;");
mv.visitInsn(Opcodes.SWAP);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, CLS, "cast", LL_SIG, false);
if (Object[].class.isAssignableFrom(cls))
mv.visitTypeInsn(Opcodes.CHECKCAST, OBJARY);
else if (PROFILE_LEVEL > 0)
*** 735,744 ****
--- 799,809 ----
* Generate an invoker method for the passed {@link LambdaForm}.
*/
private byte[] generateCustomizedCodeBytes() {
classFilePrologue();
addMethod();
+ clinit();
bogusMethod(lambdaForm);
final byte[] classFile = toByteArray();
maybeDump(classFile);
return classFile;
*** 762,779 ****
mv.visitAnnotation(FORCEINLINE_SIG, true);
} else {
mv.visitAnnotation(DONTINLINE_SIG, true);
}
! constantPlaceholder(lambdaForm); // keep LambdaForm instance & its compiled form lifetime tightly coupled.
if (lambdaForm.customized != null) {
// Since LambdaForm is customized for a particular MethodHandle, it's safe to substitute
// receiver MethodHandle (at slot #0) with an embedded constant and use it instead.
// It enables more efficient code generation in some situations, since embedded constants
// are compile-time constants for JIT compiler.
! mv.visitLdcInsn(constantPlaceholder(lambdaForm.customized));
mv.visitTypeInsn(Opcodes.CHECKCAST, MH);
assert(checkActualReceiver()); // expects MethodHandle on top of the stack
mv.visitVarInsn(Opcodes.ASTORE, localsMap[0]);
}
--- 827,844 ----
mv.visitAnnotation(FORCEINLINE_SIG, true);
} else {
mv.visitAnnotation(DONTINLINE_SIG, true);
}
! classData(lambdaForm); // keep LambdaForm instance & its compiled form lifetime tightly coupled.
if (lambdaForm.customized != null) {
// Since LambdaForm is customized for a particular MethodHandle, it's safe to substitute
// receiver MethodHandle (at slot #0) with an embedded constant and use it instead.
// It enables more efficient code generation in some situations, since embedded constants
// are compile-time constants for JIT compiler.
! mv.visitFieldInsn(Opcodes.GETSTATIC, className(), classData(lambdaForm.customized), MH_SIG);
mv.visitTypeInsn(Opcodes.CHECKCAST, MH);
assert(checkActualReceiver()); // expects MethodHandle on top of the stack
mv.visitVarInsn(Opcodes.ASTORE, localsMap[0]);
}
*** 899,909 ****
assert(!name.isLinkerMethodInvoke()); // 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));
emitReferenceCast(MethodHandle.class, target);
} else {
// load receiver
emitAloadInsn(0);
emitReferenceCast(MethodHandle.class, null);
--- 964,974 ----
assert(!name.isLinkerMethodInvoke()); // should use the static path for these
if (true) {
// push receiver
MethodHandle target = name.function.resolvedHandle();
assert(target != null) : name.exprString();
! mv.visitFieldInsn(Opcodes.GETSTATIC, className(), classData(target), MH_SIG);
emitReferenceCast(MethodHandle.class, target);
} else {
// load receiver
emitAloadInsn(0);
emitReferenceCast(MethodHandle.class, null);
*** 955,965 ****
return false; // FIXME
if (cls.isAnonymousClass() || cls.isLocalClass())
return false; // inner class of some sort
if (cls.getClassLoader() != MethodHandle.class.getClassLoader())
return false; // not on BCP
! if (ReflectUtil.isVMAnonymousClass(cls)) // FIXME: switch to supported API once it is added
return false;
if (!isStaticallyInvocableType(member.getMethodOrFieldType()))
return false;
if (!member.isPrivate() && VerifyAccess.isSamePackage(MethodHandle.class, cls))
return true; // in java.lang.invoke package
--- 1020,1032 ----
return false; // FIXME
if (cls.isAnonymousClass() || cls.isLocalClass())
return false; // inner class of some sort
if (cls.getClassLoader() != MethodHandle.class.getClassLoader())
return false; // not on BCP
! if (cls.isHiddenClass())
! return false;
! if (ReflectUtil.isVMAnonymousClass(cls)) // FIXME: Unsafe::defineAnonymousClass to be removed
return false;
if (!isStaticallyInvocableType(member.getMethodOrFieldType()))
return false;
if (!member.isPrivate() && VerifyAccess.isSamePackage(MethodHandle.class, cls))
return true; // in java.lang.invoke package
*** 979,996 ****
static boolean isStaticallyNameable(Class<?> cls) {
if (cls == Object.class)
return true;
if (MethodHandle.class.isAssignableFrom(cls)) {
! assert(!ReflectUtil.isVMAnonymousClass(cls));
return true;
}
while (cls.isArray())
cls = cls.getComponentType();
if (cls.isPrimitive())
return true; // int[].class, for example
! if (ReflectUtil.isVMAnonymousClass(cls)) // FIXME: switch to supported API once it is added
return false;
// could use VerifyAccess.isClassAccessible but the following is a safe approximation
if (cls.getClassLoader() != Object.class.getClassLoader())
return false;
if (VerifyAccess.isSamePackage(MethodHandle.class, cls))
--- 1046,1065 ----
static boolean isStaticallyNameable(Class<?> cls) {
if (cls == Object.class)
return true;
if (MethodHandle.class.isAssignableFrom(cls)) {
! assert(!cls.isHiddenClass());
return true;
}
while (cls.isArray())
cls = cls.getComponentType();
if (cls.isPrimitive())
return true; // int[].class, for example
! if (cls.isHiddenClass())
! return false;
! if (ReflectUtil.isVMAnonymousClass(cls)) // FIXME: Unsafe::defineAnonymousClass to be removed
return false;
// could use VerifyAccess.isClassAccessible but the following is a safe approximation
if (cls.getClassLoader() != Object.class.getClassLoader())
return false;
if (VerifyAccess.isSamePackage(MethodHandle.class, cls))
*** 1058,1068 ****
} catch (Throwable ex) {
throw uncaughtException(ex);
}
assert(java.lang.reflect.Array.getLength(emptyArray) == 0);
assert(emptyArray.getClass() == rtype); // exact typing
! mv.visitLdcInsn(constantPlaceholder(emptyArray));
emitReferenceCast(rtype, emptyArray);
return;
}
Class<?> arrayElementType = rtype.getComponentType();
assert(arrayElementType != null);
--- 1127,1137 ----
} catch (Throwable ex) {
throw uncaughtException(ex);
}
assert(java.lang.reflect.Array.getLength(emptyArray) == 0);
assert(emptyArray.getClass() == rtype); // exact typing
! mv.visitFieldInsn(Opcodes.GETSTATIC, className(), classData(emptyArray), "Ljava/lang/Object;");
emitReferenceCast(rtype, emptyArray);
return;
}
Class<?> arrayElementType = rtype.getComponentType();
assert(arrayElementType != null);
*** 1621,1631 ****
emitConst(arg);
} else {
if (Wrapper.isWrapperType(arg.getClass()) && bptype != L_TYPE) {
emitConst(arg);
} else {
! mv.visitLdcInsn(constantPlaceholder(arg));
emitImplicitConversion(L_TYPE, ptype, arg);
}
}
}
--- 1690,1700 ----
emitConst(arg);
} else {
if (Wrapper.isWrapperType(arg.getClass()) && bptype != L_TYPE) {
emitConst(arg);
} else {
! mv.visitFieldInsn(Opcodes.GETSTATIC, className(), classData(arg), "Ljava/lang/Object;");
emitImplicitConversion(L_TYPE, ptype, arg);
}
}
}
*** 1813,1822 ****
--- 1882,1892 ----
// return statement
emitReturnInsn(basicType(rtype));
methodEpilogue();
+ clinit();
bogusMethod(invokerType);
final byte[] classFile = cw.toByteArray();
maybeDump(classFile);
return classFile;
*** 1881,1890 ****
--- 1951,1961 ----
mv.visitInsn(Opcodes.ACONST_NULL);
}
emitReturnInsn(L_TYPE); // NOTE: NamedFunction invokers always return a reference value.
methodEpilogue();
+ clinit();
bogusMethod(dstType);
final byte[] classFile = cw.toByteArray();
maybeDump(classFile);
return classFile;
< prev index next >