From d85b0ca5cdc1820a886c46bf555b2051fed7f167 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Wed, 5 Jun 2024 15:33:03 +0000 Subject: [PATCH] 8332457: Examine startup overheads from JDK-8294961 8229959: Convert proxy class to use constant dynamic Reviewed-by: liach, redestad --- .../java/lang/constant/ConstantDescs.java | 15 +- .../classes/java/lang/reflect/Proxy.java | 2 + .../java/lang/reflect/ProxyGenerator.java | 482 +++++++----------- .../classfile/impl/StackMapDecoder.java | 30 +- .../classfile/impl/StackMapGenerator.java | 7 +- .../jdk/internal/constant/ConstantUtils.java | 3 + .../internal/constant/MethodTypeDescImpl.java | 2 +- .../constant/ReferenceClassDescImpl.java | 8 +- .../java/lang/reflect/ProxyGenBench.java | 82 +++ 9 files changed, 304 insertions(+), 327 deletions(-) create mode 100644 test/micro/org/openjdk/bench/java/lang/reflect/ProxyGenBench.java diff --git a/src/java.base/share/classes/java/lang/constant/ConstantDescs.java b/src/java.base/share/classes/java/lang/constant/ConstantDescs.java index 0e11415f910..9947b77ff83 100644 --- a/src/java.base/share/classes/java/lang/constant/ConstantDescs.java +++ b/src/java.base/share/classes/java/lang/constant/ConstantDescs.java @@ -24,6 +24,7 @@ */ package java.lang.constant; +import jdk.internal.constant.MethodTypeDescImpl; import jdk.internal.constant.PrimitiveClassDescImpl; import jdk.internal.constant.ReferenceClassDescImpl; @@ -349,8 +350,11 @@ public final class ConstantDescs { String name, ClassDesc returnType, ClassDesc... paramTypes) { - return MethodHandleDesc.ofMethod(STATIC, owner, name, MethodTypeDesc.of(returnType, paramTypes) - .insertParameterTypes(0, INDY_BOOTSTRAP_ARGS)); + int prefixLen = INDY_BOOTSTRAP_ARGS.length; + ClassDesc[] fullParamTypes = new ClassDesc[paramTypes.length + prefixLen]; + System.arraycopy(INDY_BOOTSTRAP_ARGS, 0, fullParamTypes, 0, prefixLen); + System.arraycopy(paramTypes, 0, fullParamTypes, prefixLen, paramTypes.length); + return MethodHandleDesc.ofMethod(STATIC, owner, name, MethodTypeDescImpl.ofTrusted(returnType, fullParamTypes)); } /** @@ -370,7 +374,10 @@ public final class ConstantDescs { String name, ClassDesc returnType, ClassDesc... paramTypes) { - return MethodHandleDesc.ofMethod(STATIC, owner, name, MethodTypeDesc.of(returnType, paramTypes) - .insertParameterTypes(0, CONDY_BOOTSTRAP_ARGS)); + int prefixLen = CONDY_BOOTSTRAP_ARGS.length; + ClassDesc[] fullParamTypes = new ClassDesc[paramTypes.length + prefixLen]; + System.arraycopy(CONDY_BOOTSTRAP_ARGS, 0, fullParamTypes, 0, prefixLen); + System.arraycopy(paramTypes, 0, fullParamTypes, prefixLen, paramTypes.length); + return MethodHandleDesc.ofMethod(STATIC, owner, name, MethodTypeDescImpl.ofTrusted(returnType, fullParamTypes)); } } diff --git a/src/java.base/share/classes/java/lang/reflect/Proxy.java b/src/java.base/share/classes/java/lang/reflect/Proxy.java index 24c6aaee01c..372f16926c5 100644 --- a/src/java.base/share/classes/java/lang/reflect/Proxy.java +++ b/src/java.base/share/classes/java/lang/reflect/Proxy.java @@ -856,6 +856,8 @@ public class Proxy implements java.io.Serializable { */ private static void ensureAccess(Module target, Class c) { Module m = c.getModule(); + if (target == m) return; + // add read edge and qualified export for the target module to access if (!target.canRead(m)) { Modules.addReads(target, m); diff --git a/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java b/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java index 08f971b0902..776f5df4e5a 100644 --- a/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java +++ b/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,28 +25,32 @@ package java.lang.reflect; -import java.lang.classfile.*; -import java.lang.classfile.constantpool.*; -import java.lang.classfile.attribute.ExceptionsAttribute; -import sun.security.action.GetBooleanAction; - import java.io.IOException; -import static java.lang.classfile.ClassFile.*; -import java.lang.classfile.attribute.StackMapFrameInfo; -import java.lang.classfile.attribute.StackMapTableAttribute; +import java.lang.classfile.*; +import java.lang.classfile.attribute.ExceptionsAttribute; +import java.lang.classfile.constantpool.*; import java.lang.constant.ClassDesc; -import static java.lang.constant.ConstantDescs.*; import java.lang.constant.MethodTypeDesc; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Objects; -import java.util.function.IntFunction; +import jdk.internal.constant.ConstantUtils; +import jdk.internal.constant.MethodTypeDescImpl; +import jdk.internal.constant.ReferenceClassDescImpl; +import sun.security.action.GetBooleanAction; + +import static java.lang.classfile.ClassFile.*; +import java.lang.classfile.attribute.StackMapFrameInfo; +import java.lang.classfile.attribute.StackMapTableAttribute; +import java.lang.constant.ConstantDescs; +import static java.lang.constant.ConstantDescs.*; +import java.lang.constant.DirectMethodHandleDesc; +import java.lang.constant.DynamicConstantDesc; /** * ProxyGenerator contains the code to generate a dynamic proxy class @@ -57,31 +61,32 @@ import java.util.function.IntFunction; */ final class ProxyGenerator { + private static final ClassFile CF_CONTEXT = + ClassFile.of(ClassFile.StackMapsOption.DROP_STACK_MAPS); + private static final ClassDesc - CD_ClassLoader = ClassDesc.ofInternalName("java/lang/ClassLoader"), - CD_ClassNotFoundException = ClassDesc.ofInternalName("java/lang/ClassNotFoundException"), - CD_IllegalAccessException = ClassDesc.ofInternalName("java/lang/IllegalAccessException"), - CD_InvocationHandler = ClassDesc.ofInternalName("java/lang/reflect/InvocationHandler"), - CD_Method = ClassDesc.ofInternalName("java/lang/reflect/Method"), - CD_NoClassDefFoundError = ClassDesc.ofInternalName("java/lang/NoClassDefFoundError"), - CD_NoSuchMethodError = ClassDesc.ofInternalName("java/lang/NoSuchMethodError"), - CD_NoSuchMethodException = ClassDesc.ofInternalName("java/lang/NoSuchMethodException"), - CD_Proxy = ClassDesc.ofInternalName("java/lang/reflect/Proxy"), - CD_UndeclaredThrowableException = ClassDesc.ofInternalName("java/lang/reflect/UndeclaredThrowableException"); + CD_Class_array = ReferenceClassDescImpl.ofValidated("[Ljava/lang/Class;"), + CD_IllegalAccessException = ReferenceClassDescImpl.ofValidated("Ljava/lang/IllegalAccessException;"), + CD_InvocationHandler = ReferenceClassDescImpl.ofValidated("Ljava/lang/reflect/InvocationHandler;"), + CD_Method = ReferenceClassDescImpl.ofValidated("Ljava/lang/reflect/Method;"), + CD_NoSuchMethodError = ReferenceClassDescImpl.ofValidated("Ljava/lang/NoSuchMethodError;"), + CD_NoSuchMethodException = ReferenceClassDescImpl.ofValidated("Ljava/lang/NoSuchMethodException;"), + CD_Object_array = ReferenceClassDescImpl.ofValidated("[Ljava/lang/Object;"), + CD_Proxy = ReferenceClassDescImpl.ofValidated("Ljava/lang/reflect/Proxy;"), + CD_UndeclaredThrowableException = ReferenceClassDescImpl.ofValidated("Ljava/lang/reflect/UndeclaredThrowableException;"); private static final MethodTypeDesc - MTD_boolean = MethodTypeDesc.of(CD_boolean), - MTD_void_InvocationHandler = MethodTypeDesc.of(CD_void, CD_InvocationHandler), - MTD_void_String = MethodTypeDesc.of(CD_void, CD_String), - MTD_void_Throwable = MethodTypeDesc.of(CD_void, CD_Throwable), - MTD_Class = MethodTypeDesc.of(CD_Class), - MTD_Class_String_boolean_ClassLoader = MethodTypeDesc.of(CD_Class, CD_String, CD_boolean, CD_ClassLoader), - MTD_ClassLoader = MethodTypeDesc.of(CD_ClassLoader), - MTD_MethodHandles$Lookup = MethodTypeDesc.of(CD_MethodHandles_Lookup), - MTD_MethodHandles$Lookup_MethodHandles$Lookup = MethodTypeDesc.of(CD_MethodHandles_Lookup, CD_MethodHandles_Lookup), - MTD_Method_String_ClassArray = MethodTypeDesc.of(CD_Method, CD_String, CD_Class.arrayType()), - MTD_Object_Object_Method_ObjectArray = MethodTypeDesc.of(CD_Object, CD_Object, CD_Method, CD_Object.arrayType()), - MTD_String = MethodTypeDesc.of(CD_String); + MTD_boolean = MethodTypeDescImpl.ofValidated(CD_boolean), + MTD_void_InvocationHandler = MethodTypeDescImpl.ofValidated(CD_void, CD_InvocationHandler), + MTD_void_String = MethodTypeDescImpl.ofValidated(CD_void, CD_String), + MTD_void_Throwable = MethodTypeDescImpl.ofValidated(CD_void, CD_Throwable), + MTD_Class = MethodTypeDescImpl.ofValidated(CD_Class), + MTD_Class_array = MethodTypeDescImpl.ofValidated(CD_Class_array), + MTD_Method_String_Class_array = MethodTypeDescImpl.ofValidated(CD_Method, ConstantDescs.CD_String, CD_Class_array), + MTD_MethodHandles$Lookup = MethodTypeDescImpl.ofValidated(CD_MethodHandles_Lookup), + MTD_MethodHandles$Lookup_MethodHandles$Lookup = MethodTypeDescImpl.ofValidated(CD_MethodHandles_Lookup, CD_MethodHandles_Lookup), + MTD_Object_Object_Method_ObjectArray = MethodTypeDescImpl.ofValidated(CD_Object, CD_Object, CD_Method, CD_Object_array), + MTD_String = MethodTypeDescImpl.ofValidated(CD_String); private static final String NAME_LOOKUP_ACCESSOR = "proxyClassLookup"; @@ -90,133 +95,41 @@ final class ProxyGenerator { /** * name of field for storing a proxy instance's invocation handler */ - private static final String handlerFieldName = "h"; + private static final String NAME_HANDLER_FIELD = "h"; /** * debugging flag for saving generated class files */ @SuppressWarnings("removal") - private static final boolean saveGeneratedFiles = + private static final boolean SAVE_GENERATED_FILES = java.security.AccessController.doPrivileged( new GetBooleanAction( "jdk.proxy.ProxyGenerator.saveGeneratedFiles")); /* Preloaded ProxyMethod objects for methods in java.lang.Object */ - private static final ProxyMethod hashCodeMethod; - private static final ProxyMethod equalsMethod; - private static final ProxyMethod toStringMethod; - - private static final ClassModel TEMPLATE; - - private static final ClassEntry CE_Class; - private static final ClassEntry CE_ClassNotFoundException; - private static final ClassEntry CE_NoClassDefFoundError; - private static final ClassEntry CE_NoSuchMethodError; - private static final ClassEntry CE_NoSuchMethodException; - private static final ClassEntry CE_Object; - private static final ClassEntry CE_Throwable; - private static final ClassEntry CE_UndeclaredThrowableException; - - private static final FieldRefEntry FRE_Proxy_h; - - private static final InterfaceMethodRefEntry IMRE_InvocationHandler_invoke; - - private static final MethodRefEntry MRE_Class_forName; - private static final MethodRefEntry MRE_Class_getClassLoader; - private static final MethodRefEntry MRE_Class_getMethod; - private static final MethodRefEntry MRE_NoClassDefFoundError_init; - private static final MethodRefEntry MRE_NoSuchMethodError_init; - private static final MethodRefEntry MRE_Throwable_getMessage; - private static final MethodRefEntry MRE_UndeclaredThrowableException_init; - - private static final Utf8Entry UE_Method; - - private static final List THROWABLE_STACK; - - @SuppressWarnings("unchecked") - private static T entryByIndex(int index) { - return (T) TEMPLATE.constantPool().entryByIndex(index); - } + private static final ProxyMethod HASH_CODE_METHOD; + private static final ProxyMethod EQUALS_METHOD; + private static final ProxyMethod TO_STRING_METHOD; static { - // static template ClassModel holds pre-defined constant pool entries - // proxy transformed from the template shares the template constant pool - // each direct use of the template pool entry is significantly faster - var cc = ClassFile.of(); - var ei = new int[21]; - TEMPLATE = cc.parse(cc.build(CD_Proxy, clb -> { - clb.withSuperclass(CD_Proxy); - generateConstructor(clb); - generateLookupAccessor(clb); - var cp = clb.constantPool(); - - ei[0] = cp.classEntry(CD_Class).index(); - ei[1] = cp.classEntry(CD_ClassNotFoundException).index(); - ei[2] = cp.classEntry(CD_NoClassDefFoundError).index(); - ei[3] = cp.classEntry(CD_NoSuchMethodError).index(); - ei[4] = cp.classEntry(CD_NoSuchMethodException).index(); - ei[5] = cp.classEntry(CD_Object).index(); - ei[6] = cp.classEntry(CD_Throwable).index(); - ei[7] = cp.classEntry(CD_UndeclaredThrowableException).index(); - - ei[8] = cp.fieldRefEntry(CD_Proxy, handlerFieldName, CD_InvocationHandler).index(); - - ei[9] = cp.interfaceMethodRefEntry(CD_InvocationHandler, "invoke", MTD_Object_Object_Method_ObjectArray).index(); - - ei[10] = cp.methodRefEntry(CD_Class, "forName", MTD_Class_String_boolean_ClassLoader).index(); - ei[11] = cp.methodRefEntry(CD_Class, "getClassLoader", MTD_ClassLoader).index(); - ei[12] = cp.methodRefEntry(CD_Class, "getMethod", MTD_Method_String_ClassArray).index(); - ei[13] = cp.methodRefEntry(CD_NoClassDefFoundError, INIT_NAME, MTD_void_String).index(); - ei[14] = cp.methodRefEntry(CD_NoSuchMethodError, INIT_NAME, MTD_void_String).index(); - ei[15] = cp.methodRefEntry(CD_Throwable, "getMessage", MTD_String).index(); - ei[16] = cp.methodRefEntry(CD_UndeclaredThrowableException, INIT_NAME, MTD_void_Throwable).index(); - - ei[17] = cp.utf8Entry(CD_Method).index(); - - ei[18] = cp.utf8Entry("m0").index(); - ei[19] = cp.utf8Entry("m1").index(); - ei[20] = cp.utf8Entry("m2").index(); - })); - - CE_Class = entryByIndex(ei[0]); - CE_ClassNotFoundException = entryByIndex(ei[1]); - CE_NoClassDefFoundError = entryByIndex(ei[2]); - CE_NoSuchMethodError = entryByIndex(ei[3]); - CE_NoSuchMethodException = entryByIndex(ei[4]); - CE_Object = entryByIndex(ei[5]); - CE_Throwable = entryByIndex(ei[6]); - CE_UndeclaredThrowableException = entryByIndex(ei[7]); - - FRE_Proxy_h = entryByIndex(ei[8]); - - IMRE_InvocationHandler_invoke = entryByIndex(ei[9]); - - MRE_Class_forName = entryByIndex(ei[10]); - MRE_Class_getClassLoader = entryByIndex(ei[11]); - MRE_Class_getMethod = entryByIndex(ei[12]); - MRE_NoClassDefFoundError_init = entryByIndex(ei[13]); - MRE_NoSuchMethodError_init = entryByIndex(ei[14]); - MRE_Throwable_getMessage = entryByIndex(ei[15]); - MRE_UndeclaredThrowableException_init = entryByIndex(ei[16]); - - UE_Method = entryByIndex(ei[17]); - try { - hashCodeMethod = new ProxyMethod(Object.class.getMethod("hashCode"), entryByIndex(ei[18])); - equalsMethod = new ProxyMethod(Object.class.getMethod("equals", Object.class), entryByIndex(ei[19])); - toStringMethod = new ProxyMethod(Object.class.getMethod("toString"), entryByIndex(ei[20])); + HASH_CODE_METHOD = new ProxyMethod(Object.class.getMethod("hashCode")); + EQUALS_METHOD = new ProxyMethod(Object.class.getMethod("equals", Object.class)); + TO_STRING_METHOD = new ProxyMethod(Object.class.getMethod("toString")); } catch (NoSuchMethodException e) { throw new NoSuchMethodError(e.getMessage()); } - - THROWABLE_STACK = List.of(StackMapFrameInfo.ObjectVerificationTypeInfo.of(CE_Throwable)); } - /** - * Classfile context - */ - private final ClassFile classfileContext; private final ConstantPoolBuilder cp; + private final List throwableStack; + private final NameAndTypeEntry exInit; + private final ClassEntry object, proxy, ute; + private final FieldRefEntry handlerField; + private final InterfaceMethodRefEntry invoke; + private final MethodRefEntry uteInit; + private final DirectMethodHandleDesc bsm; + /** * Name of proxy class @@ -240,12 +153,6 @@ final class ProxyGenerator { */ private final Map> proxyMethods = new LinkedHashMap<>(); - /** - * Ordinal of next ProxyMethod object added to proxyMethods. - * Indexes are reserved for hashcode(0), equals(1), toString(2). - */ - private int proxyMethodCount = 3; - /** * Construct a ProxyGenerator to generate a proxy class with the * specified name and for the given interfaces. @@ -255,14 +162,19 @@ final class ProxyGenerator { */ private ProxyGenerator(ClassLoader loader, String className, List> interfaces, int accessFlags) { - this.classfileContext = ClassFile.of( - ClassFile.StackMapsOption.DROP_STACK_MAPS, - ClassFile.ClassHierarchyResolverOption.of( - ClassHierarchyResolver.ofClassLoading(loader).cached())); - this.cp = ConstantPoolBuilder.of(TEMPLATE); - this.classEntry = cp.classEntry(ClassDesc.of(className)); + this.cp = ConstantPoolBuilder.of(); + this.classEntry = cp.classEntry(ReferenceClassDescImpl.ofValidatedBinaryName(className)); this.interfaces = interfaces; this.accessFlags = accessFlags; + this.throwableStack = List.of(StackMapFrameInfo.ObjectVerificationTypeInfo.of(cp.classEntry(CD_Throwable))); + this.exInit = cp.nameAndTypeEntry(INIT_NAME, MTD_void_String); + this.object = cp.classEntry(CD_Object); + this.proxy = cp.classEntry(CD_Proxy); + this.handlerField = cp.fieldRefEntry(proxy, cp.nameAndTypeEntry(NAME_HANDLER_FIELD, CD_InvocationHandler)); + this.invoke = cp.interfaceMethodRefEntry(CD_InvocationHandler, "invoke", MTD_Object_Object_Method_ObjectArray); + this.ute = cp.classEntry(CD_UndeclaredThrowableException); + this.uteInit = cp.methodRefEntry(ute, cp.nameAndTypeEntry(INIT_NAME, MTD_void_Throwable)); + this.bsm = ConstantDescs.ofConstantBootstrap(classEntry.asSymbol(), "$getMethod", CD_Method, CD_Class, CD_String, CD_MethodType); } /** @@ -281,7 +193,7 @@ final class ProxyGenerator { ProxyGenerator gen = new ProxyGenerator(loader, name, interfaces, accessFlags); final byte[] classFile = gen.generateClassFile(); - if (saveGeneratedFiles) { + if (SAVE_GENERATED_FILES) { java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { public Void run() { @@ -312,10 +224,10 @@ final class ProxyGenerator { * {@return the entries of the given type} * @param types the {@code Class} objects, not primitive types nor array types */ - private static ClassEntry[] toClassEntries(ConstantPoolBuilder cp, List> types) { - var ces = new ClassEntry[types.size()]; - for (int i = 0; i < ces.length; i++) - ces[i] = cp.classEntry(cp.utf8Entry(types.get(i).getName().replace('.', '/'))); + private static List toClassEntries(ConstantPoolBuilder cp, List> types) { + var ces = new ArrayList(types.size()); + for (var t : types) + ces.add(cp.classEntry(ReferenceClassDescImpl.ofValidatedBinaryName(t.getName()))); return ces; } @@ -529,9 +441,9 @@ final class ProxyGenerator { * java.lang.Object take precedence over duplicate methods in the * proxy interfaces. */ - addProxyMethod(hashCodeMethod); - addProxyMethod(equalsMethod); - addProxyMethod(toStringMethod); + addProxyMethod(HASH_CODE_METHOD); + addProxyMethod(EQUALS_METHOD); + addProxyMethod(TO_STRING_METHOD); /* * Accumulate all of the methods from the proxy interfaces. @@ -539,7 +451,7 @@ final class ProxyGenerator { for (Class intf : interfaces) { for (Method m : intf.getMethods()) { if (!Modifier.isStatic(m.getModifiers())) { - addProxyMethod(m, intf, cp); + addProxyMethod(m, intf); } } } @@ -552,22 +464,21 @@ final class ProxyGenerator { checkReturnTypes(sigmethods); } - return classfileContext.build(classEntry, cp, clb -> { - TEMPLATE.forEach(clb); + return CF_CONTEXT.build(classEntry, cp, clb -> { + clb.withSuperclass(proxy); clb.withFlags(accessFlags); clb.withInterfaces(toClassEntries(cp, interfaces)); + generateConstructor(clb); for (List sigmethods : proxyMethods.values()) { for (ProxyMethod pm : sigmethods) { - // add static field for the Method object - clb.withField(pm.methodFieldName, UE_Method, ACC_PRIVATE | ACC_STATIC | ACC_FINAL); - // Generate code for proxy method - pm.generateMethod(clb, classEntry); + pm.generateMethod(this, clb); } } - generateStaticInitializer(clb); + generateBootstrapMethod(clb); + generateLookupAccessor(clb); }); } @@ -584,7 +495,7 @@ final class ProxyGenerator { * passed to the invocation handler's "invoke" method for a given * set of duplicate methods. */ - private void addProxyMethod(Method m, Class fromClass, ConstantPoolBuilder cp) { + private void addProxyMethod(Method m, Class fromClass) { Class returnType = m.getReturnType(); Class[] exceptionTypes = m.getSharedExceptionTypes(); @@ -609,8 +520,7 @@ final class ProxyGenerator { } } sigmethods.add(new ProxyMethod(m, sig, m.getSharedParameterTypes(), returnType, - exceptionTypes, fromClass, - cp.utf8Entry("m" + proxyMethodCount++))); + exceptionTypes, fromClass)); } /** @@ -628,50 +538,36 @@ final class ProxyGenerator { /** * Generate the constructor method for the proxy class. */ - private static void generateConstructor(ClassBuilder clb) { + private void generateConstructor(ClassBuilder clb) { clb.withMethodBody(INIT_NAME, MTD_void_InvocationHandler, ACC_PUBLIC, cob -> cob - .aload(cob.receiverSlot()) - .aload(cob.parameterSlot(0)) - .invokespecial(CD_Proxy, INIT_NAME, MTD_void_InvocationHandler) + .aload(0) + .aload(1) + .invokespecial(cp.methodRefEntry(proxy, cp.nameAndTypeEntry(INIT_NAME, MTD_void_InvocationHandler))) .return_()); } /** - * Generate the static initializer method for the proxy class. + * Generate CONDY bootstrap method for the proxy class to retrieve {@link Method} instances. */ - private void generateStaticInitializer(ClassBuilder clb) { - clb.withMethodBody(CLASS_INIT_NAME, MTD_void, ACC_STATIC, cob -> { - // Put ClassLoader at local variable index 0, used by - // Class.forName(String, boolean, ClassLoader) calls - cob.ldc(classEntry) - .invokevirtual(MRE_Class_getClassLoader) - .astore(0); - var ts = cob.newBoundLabel(); - for (List sigmethods : proxyMethods.values()) { - for (ProxyMethod pm : sigmethods) { - pm.codeFieldInitialization(cob, classEntry); - } - } - cob.return_(); - var c1 = cob.newBoundLabel(); - cob.exceptionCatch(ts, c1, c1, CE_NoSuchMethodException) - .new_(CE_NoSuchMethodError) + private void generateBootstrapMethod(ClassBuilder clb) { + clb.withMethodBody(bsm.methodName(), bsm.invocationType(), ClassFile.ACC_PRIVATE | ClassFile.ACC_STATIC, cob -> { + cob.aload(3) //interface Class + .aload(4) //interface method name String + .aload(5) //interface MethodType + .invokevirtual(CD_MethodType, "parameterArray", MTD_Class_array) + .invokevirtual(ConstantDescs.CD_Class, "getMethod", MTD_Method_String_Class_array) + .areturn(); + Label failLabel = cob.newBoundLabel(); + ClassEntry nsme = cp.classEntry(CD_NoSuchMethodError); + cob.exceptionCatch(cob.startLabel(), failLabel, failLabel, CD_NoSuchMethodException) + .new_(nsme) .dup_x1() .swap() - .invokevirtual(MRE_Throwable_getMessage) - .invokespecial(MRE_NoSuchMethodError_init) - .athrow(); - var c2 = cob.newBoundLabel(); - cob.exceptionCatch(ts, c1, c2, CE_ClassNotFoundException) - .new_(CE_NoClassDefFoundError) - .dup_x1() - .swap() - .invokevirtual(MRE_Throwable_getMessage) - .invokespecial(MRE_NoClassDefFoundError_init) + .invokevirtual(cp.methodRefEntry(CD_Throwable, "getMessage", MTD_String)) + .invokespecial(cp.methodRefEntry(nsme, exInit)) .athrow() .with(StackMapTableAttribute.of(List.of( - StackMapFrameInfo.of(c1, List.of(), THROWABLE_STACK), - StackMapFrameInfo.of(c2, List.of(), THROWABLE_STACK)))); + StackMapFrameInfo.of(failLabel, List.of(), throwableStack)))); }); } @@ -680,28 +576,36 @@ final class ProxyGenerator { * on this proxy class if the caller's lookup class is java.lang.reflect.Proxy; * otherwise, IllegalAccessException is thrown */ - private static void generateLookupAccessor(ClassBuilder clb) { + private void generateLookupAccessor(ClassBuilder clb) { clb.withMethod(NAME_LOOKUP_ACCESSOR, MTD_MethodHandles$Lookup_MethodHandles$Lookup, ACC_PRIVATE | ACC_STATIC, mb -> mb.with(ExceptionsAttribute.of(List.of(mb.constantPool().classEntry(CD_IllegalAccessException)))) - .withCode(cob -> cob - .block(blockBuilder -> blockBuilder - .aload(cob.parameterSlot(0)) - .invokevirtual(CD_MethodHandles_Lookup, "lookupClass", MTD_Class) - .ldc(CD_Proxy) - .if_acmpne(blockBuilder.breakLabel()) - .aload(cob.parameterSlot(0)) - .invokevirtual(CD_MethodHandles_Lookup, "hasFullPrivilegeAccess", MTD_boolean) - .ifeq(blockBuilder.breakLabel()) - .invokestatic(CD_MethodHandles, "lookup", MTD_MethodHandles$Lookup) - .areturn()) - .new_(CD_IllegalAccessException) - .dup() - .aload(cob.parameterSlot(0)) - .invokevirtual(CD_MethodHandles_Lookup, "toString", MTD_String) - .invokespecial(CD_IllegalAccessException, INIT_NAME, MTD_void_String) - .athrow())); + .withCode(cob -> { + Label failLabel = cob.newLabel(); + ClassEntry mhl = cp.classEntry(CD_MethodHandles_Lookup); + ClassEntry iae = cp.classEntry(CD_IllegalAccessException); + cob.aload(cob.parameterSlot(0)) + .invokevirtual(cp.methodRefEntry(mhl, cp.nameAndTypeEntry("lookupClass", MTD_Class))) + .ldc(proxy) + .if_acmpne(failLabel) + .aload(cob.parameterSlot(0)) + .invokevirtual(cp.methodRefEntry(mhl, cp.nameAndTypeEntry("hasFullPrivilegeAccess", MTD_boolean))) + .ifeq(failLabel) + .invokestatic(CD_MethodHandles, "lookup", MTD_MethodHandles$Lookup) + .areturn() + .labelBinding(failLabel) + .new_(iae) + .dup() + .aload(cob.parameterSlot(0)) + .invokevirtual(cp.methodRefEntry(mhl, cp.nameAndTypeEntry("toString", MTD_String))) + .invokespecial(cp.methodRefEntry(iae, exInit)) + .athrow() + .with(StackMapTableAttribute.of(List.of( + StackMapFrameInfo.of(failLabel, + List.of(StackMapFrameInfo.ObjectVerificationTypeInfo.of(mhl)), + List.of())))); + })); } /** @@ -716,19 +620,17 @@ final class ProxyGenerator { private final Class fromClass; private final Class[] parameterTypes; private final Class returnType; - private final Utf8Entry methodFieldName; private Class[] exceptionTypes; private ProxyMethod(Method method, String sig, Class[] parameterTypes, Class returnType, Class[] exceptionTypes, - Class fromClass, Utf8Entry methodFieldName) { + Class fromClass) { this.method = method; this.shortSignature = sig; this.parameterTypes = parameterTypes; this.returnType = returnType; this.exceptionTypes = exceptionTypes; this.fromClass = fromClass; - this.methodFieldName = methodFieldName; } /** @@ -737,34 +639,39 @@ final class ProxyGenerator { * @param method The method for which to create a proxy * @param methodFieldName the fieldName to generate */ - private ProxyMethod(Method method, Utf8Entry methodFieldName) { + private ProxyMethod(Method method) { this(method, method.toShortSignature(), method.getSharedParameterTypes(), method.getReturnType(), - method.getSharedExceptionTypes(), method.getDeclaringClass(), methodFieldName); + method.getSharedExceptionTypes(), method.getDeclaringClass()); } /** * Generate this method, including the code and exception table entry. */ - private void generateMethod(ClassBuilder clb, ClassEntry className) { - var cp = clb.constantPool(); - MethodTypeDesc desc = MethodTypeDesc.of(toClassDesc(returnType), - Arrays.stream(parameterTypes).map(ProxyGenerator::toClassDesc).toArray(ClassDesc[]::new)); + private void generateMethod(ProxyGenerator pg, ClassBuilder clb) { + var cp = pg.cp; + var pTypes = new ClassDesc[parameterTypes.length]; + for (int i = 0; i < pTypes.length; i++) { + pTypes[i] = toClassDesc(parameterTypes[i]); + } + MethodTypeDesc desc = MethodTypeDescImpl.ofTrusted(toClassDesc(returnType), pTypes); int accessFlags = (method.isVarArgs()) ? ACC_VARARGS | ACC_PUBLIC | ACC_FINAL : ACC_PUBLIC | ACC_FINAL; var catchList = computeUniqueCatchList(exceptionTypes); clb.withMethod(method.getName(), desc, accessFlags, mb -> mb.with(ExceptionsAttribute.of(toClassEntries(cp, List.of(exceptionTypes)))) .withCode(cob -> { - cob.aload(cob.receiverSlot()) - .getfield(FRE_Proxy_h) - .aload(cob.receiverSlot()) - .getstatic(cp.fieldRefEntry(className, cp.nameAndTypeEntry(methodFieldName, UE_Method))); - + cob.aload(0) + .getfield(pg.handlerField) + .aload(0) + .ldc(DynamicConstantDesc.of(pg.bsm, + toClassDesc(fromClass), + method.getName(), + desc)); if (parameterTypes.length > 0) { // Create an array and fill with the parameters converting primitives to wrappers cob.loadConstant(parameterTypes.length) - .anewarray(CE_Object); + .anewarray(pg.object); for (int i = 0; i < parameterTypes.length; i++) { cob.dup() .loadConstant(i); @@ -775,7 +682,7 @@ final class ProxyGenerator { cob.aconst_null(); } - cob.invokeinterface(IMRE_InvocationHandler_invoke); + cob.invokeinterface(pg.invoke); if (returnType == void.class) { cob.pop() @@ -791,14 +698,14 @@ final class ProxyGenerator { cob.athrow(); // just rethrow the exception var c2 = cob.newBoundLabel(); cob.exceptionCatchAll(cob.startLabel(), c1, c2) - .new_(CE_UndeclaredThrowableException) + .new_(pg.ute) .dup_x1() .swap() - .invokespecial(MRE_UndeclaredThrowableException_init) + .invokespecial(pg.uteInit) .athrow() .with(StackMapTableAttribute.of(List.of( - StackMapFrameInfo.of(c1, List.of(), THROWABLE_STACK), - StackMapFrameInfo.of(c2, List.of(), THROWABLE_STACK)))); + StackMapFrameInfo.of(c1, List.of(), pg.throwableStack), + StackMapFrameInfo.of(c2, List.of(), pg.throwableStack)))); } })); } @@ -813,7 +720,7 @@ final class ProxyGenerator { if (type.isPrimitive()) { cob.loadLocal(TypeKind.from(type).asLoadable(), slot); PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(type); - cob.invokestatic(prim.wrapperMethodRef); + cob.invokestatic(prim.wrapperMethodRef(cob.constantPool())); } else { cob.aload(slot); } @@ -829,7 +736,7 @@ final class ProxyGenerator { PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(type); cob.checkcast(prim.wrapperClass) - .invokevirtual(prim.unwrapMethodRef) + .invokevirtual(prim.unwrapMethodRef(cob.constantPool())) .return_(TypeKind.from(type).asLoadable()); } else { cob.checkcast(toClassDesc(type)) @@ -837,61 +744,12 @@ final class ProxyGenerator { } } - /** - * Generate code for initializing the static field that stores - * the Method object for this proxy method. A class loader is - * anticipated at local variable index 0. - */ - private void codeFieldInitialization(CodeBuilder cob, ClassEntry className) { - var cp = cob.constantPool(); - codeClassForName(cob, fromClass); - - cob.ldc(method.getName()) - .loadConstant(parameterTypes.length) - .anewarray(CE_Class); - - // Construct an array with the parameter types mapping primitives to Wrapper types - for (int i = 0; i < parameterTypes.length; i++) { - cob.dup() - .loadConstant(i); - if (parameterTypes[i].isPrimitive()) { - PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(parameterTypes[i]); - cob.getstatic(prim.typeFieldRef); - } else { - codeClassForName(cob, parameterTypes[i]); - } - cob.aastore(); - } - // lookup the method - cob.invokevirtual(MRE_Class_getMethod) - .putstatic(cp.fieldRefEntry(className, cp.nameAndTypeEntry(methodFieldName, UE_Method))); - } - - /* - * =============== Code Generation Utility Methods =============== - */ - - /** - * Generate code to invoke the Class.forName with the name of the given - * class to get its Class object at runtime. The code is written to - * the supplied stream. Note that the code generated by this method - * may cause the checked ClassNotFoundException to be thrown. A class - * loader is anticipated at local variable index 0. - */ - private void codeClassForName(CodeBuilder cob, Class cl) { - cob.ldc(cl.getName()) - .iconst_0() // false - .aload(0)// classLoader - .invokestatic(MRE_Class_forName); - } - @Override public String toString() { return method.toShortString(); } } - private static final ConstantPoolBuilder CP = ConstantPoolBuilder.of(); /** * A PrimitiveTypeInfo object contains bytecode-related information about * a primitive type in its instance fields. The struct for a particular @@ -908,28 +766,28 @@ final class ProxyGenerator { BOOLEAN(boolean.class, CD_boolean, CD_Boolean); /** - * CP entry of corresponding wrapper class + * wrapper class */ - private final ClassEntry wrapperClass; + private final ClassDesc wrapperClass; /** - * CP entry for wrapper class "valueOf" factory method + * wrapper factory method type */ - private final MethodRefEntry wrapperMethodRef; + private final MethodTypeDesc wrapperMethodType; /** - * CP entry of wrapper class method for retrieving primitive value + * wrapper class method name for retrieving primitive value */ - private final MethodRefEntry unwrapMethodRef; + private final String unwrapMethodName; /** - * CP entry of wrapper class TYPE field + * wrapper class method type for retrieving primitive value */ - private final FieldRefEntry typeFieldRef; + private final MethodTypeDesc unwrapMethodType; PrimitiveTypeInfo(Class primitiveClass, ClassDesc baseType, ClassDesc wrapperClass) { assert baseType.isPrimitive(); - this.wrapperClass = CP.classEntry(wrapperClass); - this.wrapperMethodRef = CP.methodRefEntry(wrapperClass, "valueOf", MethodTypeDesc.of(wrapperClass, baseType)); - this.unwrapMethodRef = CP.methodRefEntry(wrapperClass, primitiveClass.getName() + "Value", MethodTypeDesc.of(baseType)); - this.typeFieldRef = CP.fieldRefEntry(wrapperClass, "TYPE", CD_Class); + this.wrapperClass = wrapperClass; + this.wrapperMethodType = MethodTypeDescImpl.ofValidated(wrapperClass, baseType); + this.unwrapMethodName = primitiveClass.getName() + "Value"; + this.unwrapMethodType = MethodTypeDescImpl.ofValidated(baseType); } public static PrimitiveTypeInfo get(Class cl) { @@ -944,5 +802,13 @@ final class ProxyGenerator { if (cl == double.class) return DOUBLE; throw new AssertionError(cl); } + + public MethodRefEntry wrapperMethodRef(ConstantPoolBuilder cp) { + return cp.methodRefEntry(wrapperClass, "valueOf", wrapperMethodType); + } + + public MethodRefEntry unwrapMethodRef(ConstantPoolBuilder cp) { + return cp.methodRefEntry(wrapperClass, unwrapMethodName, unwrapMethodType); + } } } diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapDecoder.java b/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapDecoder.java index 189c79f6c73..2ea8922d326 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapDecoder.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapDecoder.java @@ -25,21 +25,21 @@ package jdk.internal.classfile.impl; +import java.lang.classfile.BufWriter; +import java.lang.classfile.ClassReader; +import java.lang.classfile.Label; +import java.lang.classfile.MethodModel; +import java.lang.classfile.attribute.StackMapFrameInfo; +import java.lang.classfile.attribute.StackMapFrameInfo.*; +import java.lang.classfile.constantpool.ClassEntry; import java.lang.constant.ConstantDescs; import java.lang.constant.MethodTypeDesc; import java.lang.reflect.AccessFlag; import java.util.List; +import java.util.Objects; import java.util.TreeMap; -import java.lang.classfile.BufWriter; - -import java.lang.classfile.constantpool.ClassEntry; -import java.lang.classfile.attribute.StackMapFrameInfo; -import java.lang.classfile.attribute.StackMapFrameInfo.*; -import java.lang.classfile.ClassReader; import static java.lang.classfile.ClassFile.*; -import java.lang.classfile.Label; -import java.lang.classfile.MethodModel; public class StackMapDecoder { @@ -243,6 +243,20 @@ public class StackMapDecoder { @Override public int tag() { return VT_OBJECT; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof ObjectVerificationTypeInfoImpl that) { + return Objects.equals(className, that.className); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(className); + } + @Override public String toString() { return className.asInternalName(); diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapGenerator.java b/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapGenerator.java index ac512e2cecf..ba032c7b0c2 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapGenerator.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapGenerator.java @@ -1045,7 +1045,9 @@ public final class StackMapGenerator { } void setLocalsFromArg(String name, MethodTypeDesc methodDesc, boolean isStatic, Type thisKlass) { - localsSize = 0; + int localsSize = 0; + // Pre-emptively create a locals array that encompass all parameter slots + checkLocal(methodDesc.parameterCount() + (isStatic ? 0 : -1)); if (!isStatic) { localsSize++; if (OBJECT_INITIALIZER_NAME.equals(name) && !CD_Object.equals(thisKlass.sym)) { @@ -1057,7 +1059,7 @@ public final class StackMapGenerator { } for (int i = 0; i < methodDesc.parameterCount(); i++) { var desc = methodDesc.parameterType(i); - if (desc.isClassOrInterface() || desc.isArray()) { + if (!desc.isPrimitive()) { setLocalRawInternal(localsSize++, Type.referenceType(desc)); } else switch (desc.descriptorString().charAt(0)) { case 'J' -> { @@ -1075,6 +1077,7 @@ public final class StackMapGenerator { default -> throw new AssertionError("Should not reach here"); } } + this.localsSize = localsSize; } void copyFrom(Frame src) { diff --git a/src/java.base/share/classes/jdk/internal/constant/ConstantUtils.java b/src/java.base/share/classes/jdk/internal/constant/ConstantUtils.java index 8d761f4f04e..7c676a44b99 100644 --- a/src/java.base/share/classes/jdk/internal/constant/ConstantUtils.java +++ b/src/java.base/share/classes/jdk/internal/constant/ConstantUtils.java @@ -157,6 +157,9 @@ public final class ConstantUtils { throw new IllegalArgumentException("zero-length member name"); for (int i = 0; i < len; i++) { char ch = name.charAt(i); + // common case fast-path + if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) + continue; if (ch == '.' || ch == ';' || ch == '[' || ch == '/') throw new IllegalArgumentException("Invalid member name: " + name); if (method && (ch == '<' || ch == '>')) { diff --git a/src/java.base/share/classes/jdk/internal/constant/MethodTypeDescImpl.java b/src/java.base/share/classes/jdk/internal/constant/MethodTypeDescImpl.java index d6d622b419c..0a75416fe73 100644 --- a/src/java.base/share/classes/jdk/internal/constant/MethodTypeDescImpl.java +++ b/src/java.base/share/classes/jdk/internal/constant/MethodTypeDescImpl.java @@ -89,7 +89,7 @@ public final class MethodTypeDescImpl implements MethodTypeDesc { * @param returnType a {@link ClassDesc} describing the return type * @param trustedArgTypes {@link ClassDesc}s describing the trusted parameter types */ - public static MethodTypeDescImpl ofValidated(ClassDesc returnType, ClassDesc[] trustedArgTypes) { + public static MethodTypeDescImpl ofValidated(ClassDesc returnType, ClassDesc... trustedArgTypes) { if (trustedArgTypes.length == 0) return new MethodTypeDescImpl(returnType, ConstantUtils.EMPTY_CLASSDESC); return new MethodTypeDescImpl(returnType, trustedArgTypes); diff --git a/src/java.base/share/classes/jdk/internal/constant/ReferenceClassDescImpl.java b/src/java.base/share/classes/jdk/internal/constant/ReferenceClassDescImpl.java index a1efc0fd10f..b4ec9c13de5 100644 --- a/src/java.base/share/classes/jdk/internal/constant/ReferenceClassDescImpl.java +++ b/src/java.base/share/classes/jdk/internal/constant/ReferenceClassDescImpl.java @@ -126,10 +126,10 @@ public final class ReferenceClassDescImpl implements ClassDesc { @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - ClassDesc constant = (ClassDesc) o; - return descriptor.equals(constant.descriptorString()); + if (o instanceof ReferenceClassDescImpl constant) { + return descriptor.equals(constant.descriptor); + } + return false; } @Override diff --git a/test/micro/org/openjdk/bench/java/lang/reflect/ProxyGenBench.java b/test/micro/org/openjdk/bench/java/lang/reflect/ProxyGenBench.java new file mode 100644 index 00000000000..a32e46401e7 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/reflect/ProxyGenBench.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.java.lang.reflect; + + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.concurrent.TimeUnit; + +@BenchmarkMode(Mode.SingleShotTime) +@Fork(1) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 10, time = 2) +@State(Scope.Benchmark) +public class ProxyGenBench { + + static final Class[] INTF = new Class[]{Interfaze.class}; + static final IHandler HANDLER = new IHandler(); + + @Benchmark + public void generate100Proxies() { + for (int i = 0; i < 100; i++) { + Proxy.newProxyInstance(new ClsLoader(), INTF, HANDLER); + } + } + + public interface Interfaze { + default int sum(int a, int b, int c) { + return a + b + c; + } + } + + static class IHandler implements InvocationHandler { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + throw new UnsupportedOperationException(); + } + } + + static class ClsLoader extends ClassLoader { + + public ClsLoader() { + super(Interfaze.class.getClassLoader()); + } + } + + public static void main(String[] args) { + new ProxyGenBench().generate100Proxies(); + } +}