mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 06:45:07 +02:00
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:
parent
642041adbc
commit
7cc1371059
198 changed files with 9526 additions and 1575 deletions
|
@ -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();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue