8238358: Implementation of JEP 371: Hidden Classes

Co-authored-by: Lois Foltan <lois.foltan@oracle.com>
Co-authored-by: David Holmes <david.holmes@oracle.com>
Co-authored-by: Harold Seigel <harold.seigel@oracle.com>
Co-authored-by: Serguei Spitsyn <serguei.spitsyn@oracle.com>
Co-authored-by: Alex Buckley <alex.buckley@oracle.com>
Co-authored-by: Jamsheed Mohammed C M <jamsheed.c.m@oracle.com>
Co-authored-by: Jan Lahoda <jan.lahoda@oracle.com>
Co-authored-by: Amy Lu <amy.lu@oracle.com>
Reviewed-by: alanb, cjplummer, coleenp, dholmes, dlong, forax, jlahoda, psandoz, plevart, sspitsyn, vromero
This commit is contained in:
Mandy Chung 2020-04-21 06:55:38 -07:00
parent 642041adbc
commit 7cc1371059
198 changed files with 9526 additions and 1575 deletions

View file

@ -26,6 +26,7 @@
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;
@ -42,6 +43,7 @@ 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;
@ -49,6 +51,7 @@ 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.
@ -67,6 +70,8 @@ class InvokerBytecodeGenerator {
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 + ";";
@ -92,6 +97,7 @@ class InvokerBytecodeGenerator {
/** 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;
@ -99,6 +105,15 @@ class InvokerBytecodeGenerator {
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,
@ -221,41 +236,52 @@ class InvokerBytecodeGenerator {
return className;
}
class CpPatch {
final int index;
public static class ClassData {
final String name;
final String desc;
final Object value;
CpPatch(int index, Object value) {
this.index = index;
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 "CpPatch/index="+index+",value="+value;
return name + ",value="+value;
}
}
private final ArrayList<CpPatch> cpPatches = new ArrayList<>();
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;";
}
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;
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();
}
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;
List<Object> classDataValues() {
Object[] data = new Object[classData.size()];
for (int i = 0; i < classData.size(); i++) {
data[i] = classData.get(i).value;
}
return res;
return List.of(data);
}
private static String debugString(Object arg) {
@ -288,19 +314,11 @@ class InvokerBytecodeGenerator {
* Extract the MemberName of a newly-defined method.
*/
private MemberName loadMethod(byte[] classFile) {
Class<?> invokerClass = loadAndInitializeInvokerClass(classFile, cpPatches(classFile));
Class<?> invokerClass = LOOKUP.makeHiddenClassDefiner(classFile)
.defineClass(true, classDataValues());
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 {
@ -316,7 +334,8 @@ class InvokerBytecodeGenerator {
*/
private ClassWriter classFilePrologue() {
final int NOT_ACC_PUBLIC = 0; // not ACC_PUBLIC
cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
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);
@ -336,6 +355,51 @@ class InvokerBytecodeGenerator {
mv.visitEnd();
}
private String className() {
return CLASS_PREFIX + className;
}
private void clinit() {
clinit(cw, className(), classData);
}
/*
* <clinit> to initialize the static final fields with the live class data
* LambdaForms can't use condy due to bootstrapping issue.
*/
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();
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.
*/
@ -408,6 +472,10 @@ class InvokerBytecodeGenerator {
}
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) {
@ -577,8 +645,7 @@ class InvokerBytecodeGenerator {
String sig = getInternalName(cls);
mv.visitTypeInsn(Opcodes.CHECKCAST, sig);
} else {
mv.visitLdcInsn(constantPlaceholder(cls));
mv.visitTypeInsn(Opcodes.CHECKCAST, CLS);
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))
@ -737,6 +804,7 @@ class InvokerBytecodeGenerator {
private byte[] generateCustomizedCodeBytes() {
classFilePrologue();
addMethod();
clinit();
bogusMethod(lambdaForm);
final byte[] classFile = toByteArray();
@ -764,14 +832,14 @@ class InvokerBytecodeGenerator {
mv.visitAnnotation(DONTINLINE_SIG, true);
}
constantPlaceholder(lambdaForm); // keep LambdaForm instance & its compiled form lifetime tightly coupled.
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.visitLdcInsn(constantPlaceholder(lambdaForm.customized));
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]);
@ -901,7 +969,7 @@ class InvokerBytecodeGenerator {
// push receiver
MethodHandle target = name.function.resolvedHandle();
assert(target != null) : name.exprString();
mv.visitLdcInsn(constantPlaceholder(target));
mv.visitFieldInsn(Opcodes.GETSTATIC, className(), classData(target), MH_SIG);
emitReferenceCast(MethodHandle.class, target);
} else {
// load receiver
@ -957,7 +1025,9 @@ class InvokerBytecodeGenerator {
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
if (cls.isHidden())
return false;
if (ReflectUtil.isVMAnonymousClass(cls)) // FIXME: Unsafe::defineAnonymousClass to be removed
return false;
if (!isStaticallyInvocableType(member.getMethodOrFieldType()))
return false;
@ -981,14 +1051,16 @@ class InvokerBytecodeGenerator {
if (cls == Object.class)
return true;
if (MethodHandle.class.isAssignableFrom(cls)) {
assert(!ReflectUtil.isVMAnonymousClass(cls));
assert(!cls.isHidden());
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
if (cls.isHidden())
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())
@ -1060,7 +1132,7 @@ class InvokerBytecodeGenerator {
}
assert(java.lang.reflect.Array.getLength(emptyArray) == 0);
assert(emptyArray.getClass() == rtype); // exact typing
mv.visitLdcInsn(constantPlaceholder(emptyArray));
mv.visitFieldInsn(Opcodes.GETSTATIC, className(), classData(emptyArray), "Ljava/lang/Object;");
emitReferenceCast(rtype, emptyArray);
return;
}
@ -1623,7 +1695,7 @@ class InvokerBytecodeGenerator {
if (Wrapper.isWrapperType(arg.getClass()) && bptype != L_TYPE) {
emitConst(arg);
} else {
mv.visitLdcInsn(constantPlaceholder(arg));
mv.visitFieldInsn(Opcodes.GETSTATIC, className(), classData(arg), "Ljava/lang/Object;");
emitImplicitConversion(L_TYPE, ptype, arg);
}
}
@ -1815,6 +1887,7 @@ class InvokerBytecodeGenerator {
emitReturnInsn(basicType(rtype));
methodEpilogue();
clinit();
bogusMethod(invokerType);
final byte[] classFile = cw.toByteArray();
@ -1883,6 +1956,7 @@ class InvokerBytecodeGenerator {
emitReturnInsn(L_TYPE); // NOTE: NamedFunction invokers always return a reference value.
methodEpilogue();
clinit();
bogusMethod(dstType);
final byte[] classFile = cw.toByteArray();