From 0267d6578ebbf6f12159a0f5fa7874a52a020ef4 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Fri, 15 Dec 2023 13:52:34 +0100 Subject: [PATCH 01/23] 8294961: Convert java.base/java.lang.reflect.ProxyGenerator to use the Classfile API to generate proxy classes --- .../classes/java/lang/reflect/Proxy.java | 1 + .../java/lang/reflect/ProxyGenerator.java | 677 +++++++----------- 2 files changed, 253 insertions(+), 425 deletions(-) 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 3c37153b875ae..24c6aaee01ce0 100644 --- a/src/java.base/share/classes/java/lang/reflect/Proxy.java +++ b/src/java.base/share/classes/java/lang/reflect/Proxy.java @@ -618,6 +618,7 @@ private static boolean isDebug(String flag) { private final List> interfaces; private final ProxyClassContext context; ProxyBuilder(ClassLoader loader, List> interfaces) { + Objects.requireNonNull(interfaces); if (!VM.isModuleSystemInited()) { throw new InternalError("Proxy is not supported until " + "module system is fully initialized"); 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 8911ba1190320..d69b4ce4c6387 100644 --- a/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java +++ b/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java @@ -25,16 +25,15 @@ package java.lang.reflect; -import jdk.internal.org.objectweb.asm.ClassWriter; -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.Wrapper; +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 java.lang.invoke.MethodType; +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; @@ -43,8 +42,9 @@ import java.util.List; import java.util.ListIterator; import java.util.Map; +import java.util.Objects; -import static jdk.internal.org.objectweb.asm.Opcodes.*; +import static java.lang.classfile.ClassFile.*; /** * ProxyGenerator contains the code to generate a dynamic proxy class @@ -53,33 +53,35 @@ * The external interface to ProxyGenerator is the static * "generateProxyClass" method. */ -final class ProxyGenerator extends ClassWriter { +final class ProxyGenerator { private static final int CLASSFILE_VERSION = ClassFileFormatVersion.latest().major(); - private static final String JL_CLASS = "java/lang/Class"; - private static final String JL_OBJECT = "java/lang/Object"; - private static final String JL_THROWABLE = "java/lang/Throwable"; - private static final String JL_CLASS_NOT_FOUND_EX = "java/lang/ClassNotFoundException"; - private static final String JL_ILLEGAL_ACCESS_EX = "java/lang/IllegalAccessException"; - - private static final String JL_NO_CLASS_DEF_FOUND_ERROR = "java/lang/NoClassDefFoundError"; - private static final String JL_NO_SUCH_METHOD_EX = "java/lang/NoSuchMethodException"; - private static final String JL_NO_SUCH_METHOD_ERROR = "java/lang/NoSuchMethodError"; - private static final String JLI_LOOKUP = "java/lang/invoke/MethodHandles$Lookup"; - private static final String JLI_METHODHANDLES = "java/lang/invoke/MethodHandles"; - - private static final String JLR_INVOCATION_HANDLER = "java/lang/reflect/InvocationHandler"; - private static final String JLR_PROXY = "java/lang/reflect/Proxy"; - private static final String JLR_UNDECLARED_THROWABLE_EX = "java/lang/reflect/UndeclaredThrowableException"; - - private static final String LJL_CLASS = "Ljava/lang/Class;"; - private static final String LJL_CLASSLOADER = "Ljava/lang/ClassLoader;"; - private static final String LJLR_METHOD = "Ljava/lang/reflect/Method;"; - private static final String LJLR_INVOCATION_HANDLER = "Ljava/lang/reflect/InvocationHandler;"; - - private static final String MJLR_INVOCATIONHANDLER = "(Ljava/lang/reflect/InvocationHandler;)V"; - - private static final String NAME_CTOR = ""; - private static final String NAME_CLINIT = ""; + + 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"); + + 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); + private static final String NAME_LOOKUP_ACCESSOR = "proxyClassLookup"; private static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; @@ -114,14 +116,14 @@ final class ProxyGenerator extends ClassWriter { } /** - * Class loader + * Classfile context */ - private final ClassLoader loader; + private final ClassFile classfileContext; /** * Name of proxy class */ - private final String className; + private final ClassDesc classDesc; /** * Proxy interfaces @@ -155,9 +157,10 @@ final class ProxyGenerator extends ClassWriter { */ private ProxyGenerator(ClassLoader loader, String className, List> interfaces, int accessFlags) { - super(ClassWriter.COMPUTE_FRAMES); - this.loader = loader; - this.className = className; + this.classfileContext = ClassFile.of( + ClassFile.ClassHierarchyResolverOption.of( + ClassHierarchyResolver.ofClassLoading(loader).cached())); + this.classDesc = ClassDesc.of(className); this.interfaces = interfaces; this.accessFlags = accessFlags; } @@ -174,6 +177,7 @@ static byte[] generateProxyClass(ClassLoader loader, final String name, List> interfaces, int accessFlags) { + Objects.requireNonNull(interfaces); ProxyGenerator gen = new ProxyGenerator(loader, name, interfaces, accessFlags); final byte[] classFile = gen.generateClassFile(); @@ -185,7 +189,7 @@ public Void run() { int i = name.lastIndexOf('.'); Path path; if (i > 0) { - Path dir = Path.of(dotToSlash(name.substring(0, i))); + Path dir = Path.of(name.substring(0, i).replace('.', '/')); Files.createDirectories(dir); path = dir.resolve(name.substring(i + 1) + ".class"); } else { @@ -205,20 +209,11 @@ public Void run() { } /** - * Return an array of the class and interface names from an array of Classes. - * - * @param classes an array of classes or interfaces - * @return the array of class and interface names; or null if classes is - * null or empty + * {@return the {@code ClassDesc} of the given type} + * @param type the {@code Class} object */ - private static String[] typeNames(List> classes) { - if (classes == null || classes.size() == 0) - return null; - int size = classes.size(); - String[] ifaces = new String[size]; - for (int i = 0; i < size; i++) - ifaces[i] = dotToSlash(classes.get(i).getName()); - return ifaces; + private static ClassDesc toClassDesc(Class type) { + return ClassDesc.ofDescriptor(type.descriptorString()); } /** @@ -336,7 +331,7 @@ private static void checkReturnTypes(List methods) { * given list of declared exceptions, indicating that no exceptions * need to be caught. */ - private static List> computeUniqueCatchList(Class[] exceptions) { + private static List computeUniqueCatchList(Class[] exceptions) { List> uniqueList = new ArrayList<>(); // unique exceptions to catch @@ -384,35 +379,7 @@ private static List> computeUniqueCatchList(Class[] exceptions) { // This exception is unique (so far): add it to the list to catch. uniqueList.add(ex); } - return uniqueList; - } - - /** - * Convert a fully qualified class name that uses '.' as the package - * separator, the external representation used by the Java language - * and APIs, to a fully qualified class name that uses '/' as the - * package separator, the representation used in the class file - * format (see JVMS section {@jvms 4.2}). - */ - private static String dotToSlash(String name) { - return name.replace('.', '/'); - } - - /** - * Return the number of abstract "words", or consecutive local variable - * indexes, required to contain a value of the given type. See JVMS - * section {@jvms 3.6.1}. - *

- * Note that the original version of the JVMS contained a definition of - * this abstract notion of a "word" in section 3.4, but that definition - * was removed for the second edition. - */ - private static int getWordsPerType(Class type) { - if (type == long.class || type == double.class) { - return 2; - } else { - return 1; - } + return uniqueList.stream().map(ProxyGenerator::toClassDesc).toList(); } /** @@ -439,71 +406,62 @@ private static void collectCompatibleTypes(Class[] from, } } - /** - * Returns the {@link ClassLoader} to be used by the default implementation of {@link - * #getCommonSuperClass(String, String)}, that of this {@link ClassWriter}'s runtime type by - * default. - * - * @return ClassLoader - */ - protected ClassLoader getClassLoader() { - return loader; - } - /** * Generate a class file for the proxy class. This method drives the * class file generation process. */ private byte[] generateClassFile() { - visit(CLASSFILE_VERSION, accessFlags, dotToSlash(className), null, - JLR_PROXY, typeNames(interfaces)); + return classfileContext.build(classDesc, clb -> { + clb.withFlags(accessFlags); + clb.withSuperclass(CD_Proxy); + clb.withInterfaceSymbols(interfaces.stream().map(ProxyGenerator::toClassDesc).toList()); + clb.withVersion(CLASSFILE_VERSION, 0); - /* - * Add proxy methods for the hashCode, equals, - * and toString methods of java.lang.Object. This is done before - * the methods from the proxy interfaces so that the methods from - * java.lang.Object take precedence over duplicate methods in the - * proxy interfaces. - */ - addProxyMethod(hashCodeMethod); - addProxyMethod(equalsMethod); - addProxyMethod(toStringMethod); + /* + * Add proxy methods for the hashCode, equals, + * and toString methods of java.lang.Object. This is done before + * the methods from the proxy interfaces so that the methods from + * java.lang.Object take precedence over duplicate methods in the + * proxy interfaces. + */ + addProxyMethod(hashCodeMethod); + addProxyMethod(equalsMethod); + addProxyMethod(toStringMethod); - /* - * Accumulate all of the methods from the proxy interfaces. - */ - for (Class intf : interfaces) { - for (Method m : intf.getMethods()) { - if (!Modifier.isStatic(m.getModifiers())) { - addProxyMethod(m, intf); + /* + * Accumulate all of the methods from the proxy interfaces. + */ + for (Class intf : interfaces) { + for (Method m : intf.getMethods()) { + if (!Modifier.isStatic(m.getModifiers())) { + addProxyMethod(m, intf); + } } } - } - /* - * For each set of proxy methods with the same signature, - * verify that the methods' return types are compatible. - */ - for (List sigmethods : proxyMethods.values()) { - checkReturnTypes(sigmethods); - } + /* + * For each set of proxy methods with the same signature, + * verify that the methods' return types are compatible. + */ + for (List sigmethods : proxyMethods.values()) { + checkReturnTypes(sigmethods); + } - generateConstructor(); + generateConstructor(clb); - for (List sigmethods : proxyMethods.values()) { - for (ProxyMethod pm : sigmethods) { - // add static field for the Method object - visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL, pm.methodFieldName, - LJLR_METHOD, null, null); + for (List sigmethods : proxyMethods.values()) { + for (ProxyMethod pm : sigmethods) { + // add static field for the Method object + clb.withField(pm.methodFieldName, CD_Method, ACC_PRIVATE | ACC_STATIC | ACC_FINAL); - // Generate code for proxy method - pm.generateMethod(this, className); + // Generate code for proxy method + pm.generateMethod(clb, classDesc); + } } - } - generateStaticInitializer(); - generateLookupAccessor(); - return toByteArray(); + generateStaticInitializer(clb); + generateLookupAccessor(clb); + }); } /** @@ -563,82 +521,46 @@ private void addProxyMethod(ProxyMethod pm) { /** * Generate the constructor method for the proxy class. */ - private void generateConstructor() { - MethodVisitor ctor = visitMethod(Modifier.PUBLIC, NAME_CTOR, - MJLR_INVOCATIONHANDLER, null, null); - ctor.visitParameter(null, 0); - ctor.visitCode(); - ctor.visitVarInsn(ALOAD, 0); - ctor.visitVarInsn(ALOAD, 1); - ctor.visitMethodInsn(INVOKESPECIAL, JLR_PROXY, NAME_CTOR, - MJLR_INVOCATIONHANDLER, false); - ctor.visitInsn(RETURN); - - // Maxs computed by ClassWriter.COMPUTE_FRAMES, these arguments ignored - ctor.visitMaxs(-1, -1); - ctor.visitEnd(); + 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) + .return_()); } /** * Generate the static initializer method for the proxy class. */ - private void generateStaticInitializer() { - - MethodVisitor mv = visitMethod(Modifier.STATIC, NAME_CLINIT, - "()V", null, null); - mv.visitCode(); - Label L_startBlock = new Label(); - Label L_endBlock = new Label(); - Label L_NoMethodHandler = new Label(); - Label L_NoClassHandler = new Label(); - - mv.visitTryCatchBlock(L_startBlock, L_endBlock, L_NoMethodHandler, - JL_NO_SUCH_METHOD_EX); - mv.visitTryCatchBlock(L_startBlock, L_endBlock, L_NoClassHandler, - JL_CLASS_NOT_FOUND_EX); - - // Put ClassLoader at local variable index 0, used by - // Class.forName(String, boolean, ClassLoader) calls - mv.visitLdcInsn(Type.getObjectType(dotToSlash(className))); - mv.visitMethodInsn(INVOKEVIRTUAL, JL_CLASS, - "getClassLoader", "()" + LJL_CLASSLOADER, false); - mv.visitVarInsn(ASTORE, 0); - - mv.visitLabel(L_startBlock); - for (List sigmethods : proxyMethods.values()) { - for (ProxyMethod pm : sigmethods) { - pm.codeFieldInitialization(mv, className); - } - } - mv.visitInsn(RETURN); - mv.visitLabel(L_endBlock); - // Generate exception handler - - mv.visitLabel(L_NoMethodHandler); - mv.visitVarInsn(ASTORE, 1); - mv.visitTypeInsn(Opcodes.NEW, JL_NO_SUCH_METHOD_ERROR); - mv.visitInsn(DUP); - mv.visitVarInsn(ALOAD, 1); - mv.visitMethodInsn(INVOKEVIRTUAL, JL_THROWABLE, - "getMessage", "()Ljava/lang/String;", false); - mv.visitMethodInsn(INVOKESPECIAL, JL_NO_SUCH_METHOD_ERROR, - "", "(Ljava/lang/String;)V", false); - mv.visitInsn(ATHROW); - - mv.visitLabel(L_NoClassHandler); - mv.visitVarInsn(ASTORE, 1); - mv.visitTypeInsn(Opcodes.NEW, JL_NO_CLASS_DEF_FOUND_ERROR); - mv.visitInsn(DUP); - mv.visitVarInsn(ALOAD, 1); - mv.visitMethodInsn(INVOKEVIRTUAL, JL_THROWABLE, - "getMessage", "()Ljava/lang/String;", false); - mv.visitMethodInsn(INVOKESPECIAL, JL_NO_CLASS_DEF_FOUND_ERROR, - "", "(Ljava/lang/String;)V", false); - mv.visitInsn(ATHROW); - - // Maxs computed by ClassWriter.COMPUTE_FRAMES, these arguments ignored - mv.visitMaxs(-1, -1); - mv.visitEnd(); + private void generateStaticInitializer(ClassBuilder clb) { + clb.withMethodBody(CLASS_INIT_NAME, MTD_void, ACC_STATIC, cob -> cob + // Put ClassLoader at local variable index 0, used by + // Class.forName(String, boolean, ClassLoader) calls + .constantInstruction(classDesc) + .invokevirtual(CD_Class, "getClassLoader", MTD_ClassLoader) + .astore(0) + .trying(tryb -> { + for (List sigmethods : proxyMethods.values()) { + for (ProxyMethod pm : sigmethods) { + pm.codeFieldInitialization(tryb, classDesc); + } + } + tryb.return_(); + }, cb -> cb + .catching(CD_NoSuchMethodException, nsmb -> nsmb + .new_(CD_NoSuchMethodError) + .dup_x1() + .swap() + .invokevirtual(CD_Throwable, "getMessage", MTD_String) + .invokespecial(CD_NoSuchMethodError, INIT_NAME, MTD_void_String) + .athrow()) + .catching(CD_ClassNotFoundException, cnfb -> cnfb + .new_(CD_NoClassDefFoundError) + .dup_x1() + .swap() + .invokevirtual(CD_Throwable, "getMessage", MTD_String) + .invokespecial(CD_NoClassDefFoundError, INIT_NAME, MTD_void_String) + .athrow()))); } /** @@ -646,39 +568,28 @@ private void generateStaticInitializer() { * on this proxy class if the caller's lookup class is java.lang.reflect.Proxy; * otherwise, IllegalAccessException is thrown */ - private void generateLookupAccessor() { - MethodVisitor mv = visitMethod(ACC_PRIVATE | ACC_STATIC, NAME_LOOKUP_ACCESSOR, - "(Ljava/lang/invoke/MethodHandles$Lookup;)Ljava/lang/invoke/MethodHandles$Lookup;", null, - new String[] { JL_ILLEGAL_ACCESS_EX }); - mv.visitCode(); - Label L_illegalAccess = new Label(); - - mv.visitVarInsn(ALOAD, 0); - mv.visitMethodInsn(INVOKEVIRTUAL, JLI_LOOKUP, "lookupClass", - "()Ljava/lang/Class;", false); - mv.visitLdcInsn(Type.getType(Proxy.class)); - mv.visitJumpInsn(IF_ACMPNE, L_illegalAccess); - mv.visitVarInsn(ALOAD, 0); - mv.visitMethodInsn(INVOKEVIRTUAL, JLI_LOOKUP, "hasFullPrivilegeAccess", - "()Z", false); - mv.visitJumpInsn(IFEQ, L_illegalAccess); - mv.visitMethodInsn(INVOKESTATIC, JLI_METHODHANDLES, "lookup", - "()Ljava/lang/invoke/MethodHandles$Lookup;", false); - mv.visitInsn(ARETURN); - - mv.visitLabel(L_illegalAccess); - mv.visitTypeInsn(Opcodes.NEW, JL_ILLEGAL_ACCESS_EX); - mv.visitInsn(DUP); - mv.visitVarInsn(ALOAD, 0); - mv.visitMethodInsn(INVOKEVIRTUAL, JLI_LOOKUP, "toString", - "()Ljava/lang/String;", false); - mv.visitMethodInsn(INVOKESPECIAL, JL_ILLEGAL_ACCESS_EX, - "", "(Ljava/lang/String;)V", false); - mv.visitInsn(ATHROW); - - // Maxs computed by ClassWriter.COMPUTE_FRAMES, these arguments ignored - mv.visitMaxs(-1, -1); - mv.visitEnd(); + 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) + .constantInstruction(Opcode.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())); } /** @@ -716,98 +627,70 @@ private ProxyMethod(Method method, String sig, Class[] parameterTypes, */ private ProxyMethod(Method method, String methodFieldName) { this(method, method.toShortSignature(), - method.getSharedParameterTypes(), method.getReturnType(), - method.getSharedExceptionTypes(), method.getDeclaringClass(), methodFieldName); + method.getSharedParameterTypes(), method.getReturnType(), + method.getSharedExceptionTypes(), method.getDeclaringClass(), methodFieldName); } /** * Generate this method, including the code and exception table entry. */ - private void generateMethod(ClassWriter cw, String className) { - MethodType mt = MethodType.methodType(returnType, parameterTypes); - String desc = mt.toMethodDescriptorString(); - int accessFlags = ACC_PUBLIC | ACC_FINAL; - if (method.isVarArgs()) accessFlags |= ACC_VARARGS; - - MethodVisitor mv = cw.visitMethod(accessFlags, - method.getName(), desc, null, - typeNames(Arrays.asList(exceptionTypes))); - - int[] parameterSlot = new int[parameterTypes.length]; - int nextSlot = 1; - for (int i = 0; i < parameterSlot.length; i++) { - parameterSlot[i] = nextSlot; - nextSlot += getWordsPerType(parameterTypes[i]); - } - - mv.visitCode(); - Label L_startBlock = new Label(); - Label L_endBlock = new Label(); - Label L_RuntimeHandler = new Label(); - Label L_ThrowableHandler = new Label(); - - List> catchList = computeUniqueCatchList(exceptionTypes); - if (catchList.size() > 0) { - for (Class ex : catchList) { - mv.visitTryCatchBlock(L_startBlock, L_endBlock, L_RuntimeHandler, - dotToSlash(ex.getName())); - } - - mv.visitTryCatchBlock(L_startBlock, L_endBlock, L_ThrowableHandler, - JL_THROWABLE); - } - mv.visitLabel(L_startBlock); - - mv.visitVarInsn(ALOAD, 0); - mv.visitFieldInsn(GETFIELD, JLR_PROXY, handlerFieldName, - LJLR_INVOCATION_HANDLER); - mv.visitVarInsn(ALOAD, 0); - mv.visitFieldInsn(GETSTATIC, dotToSlash(className), methodFieldName, - LJLR_METHOD); - - if (parameterTypes.length > 0) { - // Create an array and fill with the parameters converting primitives to wrappers - emitIconstInsn(mv, parameterTypes.length); - mv.visitTypeInsn(Opcodes.ANEWARRAY, JL_OBJECT); - for (int i = 0; i < parameterTypes.length; i++) { - mv.visitInsn(DUP); - emitIconstInsn(mv, i); - codeWrapArgument(mv, parameterTypes[i], parameterSlot[i]); - mv.visitInsn(Opcodes.AASTORE); - } - } else { - mv.visitInsn(Opcodes.ACONST_NULL); - } - - mv.visitMethodInsn(INVOKEINTERFACE, JLR_INVOCATION_HANDLER, - "invoke", - "(Ljava/lang/Object;Ljava/lang/reflect/Method;" + - "[Ljava/lang/Object;)Ljava/lang/Object;", true); + private void generateMethod(ClassBuilder clb, ClassDesc className) { + MethodTypeDesc desc = MethodTypeDesc.of(toClassDesc(returnType), + Arrays.stream(parameterTypes).map(ProxyGenerator::toClassDesc).toArray(ClassDesc[]::new)); + 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 -> { + ConstantPoolBuilder cpb = mb.constantPool(); + List exceptionClassEntries = Arrays.asList(exceptionTypes) + .stream() + .map(ProxyGenerator::toClassDesc) + .map(cpb::classEntry) + .toList(); + mb.with(ExceptionsAttribute.of(exceptionClassEntries)); + mb.withCode(cob -> + cob.trying(tryb -> { + tryb.aload(tryb.receiverSlot()) + .getfield(CD_Proxy, handlerFieldName, CD_InvocationHandler) + .aload(tryb.receiverSlot()) + .getstatic(className, methodFieldName, CD_Method); + + if (parameterTypes.length > 0) { + // Create an array and fill with the parameters converting primitives to wrappers + tryb.constantInstruction(parameterTypes.length) + .anewarray(CD_Object); + for (int i = 0; i < parameterTypes.length; i++) { + tryb.dup() + .constantInstruction(i); + codeWrapArgument(tryb, parameterTypes[i], tryb.parameterSlot(i)); + tryb.aastore(); + } + } else { + tryb.aconst_null(); + } - if (returnType == void.class) { - mv.visitInsn(POP); - mv.visitInsn(RETURN); - } else { - codeUnwrapReturnValue(mv, returnType); - } + tryb.invokeinterface(CD_InvocationHandler, "invoke", MTD_Object_Object_Method_ObjectArray); - mv.visitLabel(L_endBlock); - - // Generate exception handler - mv.visitLabel(L_RuntimeHandler); - mv.visitInsn(ATHROW); // just rethrow the exception - - mv.visitLabel(L_ThrowableHandler); - mv.visitVarInsn(ASTORE, 1); - mv.visitTypeInsn(Opcodes.NEW, JLR_UNDECLARED_THROWABLE_EX); - mv.visitInsn(DUP); - mv.visitVarInsn(ALOAD, 1); - mv.visitMethodInsn(INVOKESPECIAL, JLR_UNDECLARED_THROWABLE_EX, - "", "(Ljava/lang/Throwable;)V", false); - mv.visitInsn(ATHROW); - // Maxs computed by ClassWriter.COMPUTE_FRAMES, these arguments ignored - mv.visitMaxs(-1, -1); - mv.visitEnd(); + if (returnType == void.class) { + tryb.pop() + .return_(); + } else { + codeUnwrapReturnValue(tryb, returnType); + } + }, catchBuilder -> { + if (!catchList.isEmpty()) { + catchBuilder.catchingMulti(catchList, ehb -> ehb + .athrow()); // just rethrow the exception + + catchBuilder.catching(CD_Throwable, ehb -> ehb + .new_(CD_UndeclaredThrowableException) + .dup_x1() + .swap() + .invokespecial(CD_UndeclaredThrowableException, INIT_NAME, MTD_void_Throwable) + .athrow()); + } + })); + }); } /** @@ -816,15 +699,13 @@ private void generateMethod(ClassWriter cw, String className) { * index, in order for it to be passed (as an Object) to the * invocation handler's "invoke" method. */ - private void codeWrapArgument(MethodVisitor mv, Class type, int slot) { + private void codeWrapArgument(CodeBuilder cob, Class type, int slot) { if (type.isPrimitive()) { + cob.loadInstruction(TypeKind.from(type).asLoadable(), slot); PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(type); - - mv.visitVarInsn(prim.loadOpcode, slot); - mv.visitMethodInsn(INVOKESTATIC, prim.wrapperClassName, "valueOf", - prim.wrapperValueOfDesc, false); + cob.invokestatic(prim.wrapperClass, "valueOf", prim.wrapperValueOf); } else { - mv.visitVarInsn(ALOAD, slot); + cob.aload(slot); } } @@ -833,19 +714,16 @@ private void codeWrapArgument(MethodVisitor mv, Class type, int slot) { * type from the invocation handler's "invoke" method (as type * Object) to its correct type. */ - private void codeUnwrapReturnValue(MethodVisitor mv, Class type) { + private void codeUnwrapReturnValue(CodeBuilder cob, Class type) { if (type.isPrimitive()) { PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(type); - mv.visitTypeInsn(CHECKCAST, prim.wrapperClassName); - mv.visitMethodInsn(INVOKEVIRTUAL, - prim.wrapperClassName, - prim.unwrapMethodName, prim.unwrapMethodDesc, false); - - mv.visitInsn(prim.returnOpcode); + cob.typeCheckInstruction(Opcode.CHECKCAST, prim.wrapperClass) + .invokevirtual(prim.wrapperClass, prim.unwrapMethodName, prim.unwrapMethodType) + .returnInstruction(TypeKind.from(type).asLoadable()); } else { - mv.visitTypeInsn(CHECKCAST, dotToSlash(type.getName())); - mv.visitInsn(ARETURN); + cob.checkcast(toClassDesc(type)) + .areturn(); } } @@ -854,40 +732,28 @@ private void codeUnwrapReturnValue(MethodVisitor mv, Class type) { * the Method object for this proxy method. A class loader is * anticipated at local variable index 0. */ - private void codeFieldInitialization(MethodVisitor mv, String className) { - codeClassForName(mv, fromClass); - - mv.visitLdcInsn(method.getName()); - - emitIconstInsn(mv, parameterTypes.length); + private void codeFieldInitialization(CodeBuilder cob, ClassDesc className) { + codeClassForName(cob, fromClass); - mv.visitTypeInsn(Opcodes.ANEWARRAY, JL_CLASS); + cob.ldc(method.getName()) + .constantInstruction(parameterTypes.length) + .anewarray(CD_Class); // Construct an array with the parameter types mapping primitives to Wrapper types for (int i = 0; i < parameterTypes.length; i++) { - mv.visitInsn(DUP); - emitIconstInsn(mv, i); - + cob.dup() + .constantInstruction(i); if (parameterTypes[i].isPrimitive()) { - PrimitiveTypeInfo prim = - PrimitiveTypeInfo.get(parameterTypes[i]); - mv.visitFieldInsn(GETSTATIC, - prim.wrapperClassName, "TYPE", LJL_CLASS); + PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(parameterTypes[i]); + cob.getstatic(prim.wrapperClass, "TYPE", CD_Class); } else { - codeClassForName(mv, parameterTypes[i]); + codeClassForName(cob, parameterTypes[i]); } - mv.visitInsn(Opcodes.AASTORE); + cob.aastore(); } // lookup the method - mv.visitMethodInsn(INVOKEVIRTUAL, - JL_CLASS, - "getMethod", - "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", - false); - - mv.visitFieldInsn(PUTSTATIC, - dotToSlash(className), - methodFieldName, LJLR_METHOD); + cob.invokevirtual(CD_Class, "getMethod", MTD_Method_String_ClassArray) + .putstatic(className, methodFieldName, CD_Method); } /* @@ -901,33 +767,11 @@ private void codeFieldInitialization(MethodVisitor mv, String className) { * may cause the checked ClassNotFoundException to be thrown. A class * loader is anticipated at local variable index 0. */ - private void codeClassForName(MethodVisitor mv, Class cl) { - mv.visitLdcInsn(cl.getName()); - mv.visitInsn(ICONST_0); // false - mv.visitVarInsn(ALOAD, 0); // classLoader - mv.visitMethodInsn(INVOKESTATIC, - JL_CLASS, - "forName", - "(Ljava/lang/String;Z" + LJL_CLASSLOADER + ")Ljava/lang/Class;", - false); - } - - /** - * Visit a bytecode for a constant. - * - * @param mv The MethodVisitor - * @param cst The constant value - */ - private void emitIconstInsn(MethodVisitor mv, final 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) { - mv.visitIntInsn(Opcodes.SIPUSH, cst); - } else { - mv.visitLdcInsn(cst); - } + private void codeClassForName(CodeBuilder cob, Class cl) { + cob.constantInstruction(Opcode.LDC, cl.getName()) + .iconst_0() // false + .aload(0)// classLoader + .invokestatic(CD_Class, "forName", MTD_Class_String_boolean_ClassLoader); } @Override @@ -942,23 +786,23 @@ public String toString() { * primitive type can be obtained using the static "get" method. */ private enum PrimitiveTypeInfo { - BYTE(byte.class, ILOAD, IRETURN), - CHAR(char.class, ILOAD, IRETURN), - DOUBLE(double.class, DLOAD, DRETURN), - FLOAT(float.class, FLOAD, FRETURN), - INT(int.class, ILOAD, IRETURN), - LONG(long.class, LLOAD, LRETURN), - SHORT(short.class, ILOAD, IRETURN), - BOOLEAN(boolean.class, ILOAD, IRETURN); + BYTE(byte.class, CD_byte, CD_Byte), + CHAR(char.class, CD_char, CD_Character), + DOUBLE(double.class, CD_double, CD_Double), + FLOAT(float.class, CD_float, CD_Float), + INT(int.class, CD_int, CD_Integer), + LONG(long.class, CD_long, CD_Long), + SHORT(short.class, CD_short, CD_Short), + BOOLEAN(boolean.class, CD_boolean, CD_Boolean); /** * internal name of corresponding wrapper class */ - private final String wrapperClassName; + private final ClassDesc wrapperClass; /** * method descriptor for wrapper class "valueOf" factory method */ - private final String wrapperValueOfDesc; + private final MethodTypeDesc wrapperValueOf; /** * name of wrapper class method for retrieving primitive value */ @@ -966,31 +810,14 @@ private enum PrimitiveTypeInfo { /** * descriptor of same method */ - private final String unwrapMethodDesc; - /** - * Load opcode used by this primitive - */ - private final int loadOpcode; - /** - * Return opcode used by this primitive - */ - private final int returnOpcode; - - PrimitiveTypeInfo(Class primitiveClass, int loadOpcode, int returnOpcode) { - assert primitiveClass.isPrimitive(); - assert returnOpcode - IRETURN == loadOpcode - ILOAD; - - Wrapper wrapper = Wrapper.forPrimitiveType(primitiveClass); - // single-char BaseType descriptor (see JVMS section 4.3.2) - String baseTypeString = wrapper.basicTypeString(); - var wrapperType = wrapper.wrapperType(); - wrapperClassName = dotToSlash(wrapperType.getName()); - wrapperValueOfDesc = - "(" + baseTypeString + ")" + wrapperType.descriptorString(); - unwrapMethodName = primitiveClass.getName() + "Value"; - unwrapMethodDesc = "()" + baseTypeString; - this.loadOpcode = loadOpcode; - this.returnOpcode = returnOpcode; + private final MethodTypeDesc unwrapMethodType; + + PrimitiveTypeInfo(Class primitiveClass, ClassDesc baseType, ClassDesc wrapperClass) { + assert baseType.isPrimitive(); + this.wrapperClass = wrapperClass; + this.wrapperValueOf = MethodTypeDesc.of(wrapperClass, baseType); + this.unwrapMethodName = primitiveClass.getName() + "Value"; + this.unwrapMethodType = MethodTypeDesc.of(baseType); } public static PrimitiveTypeInfo get(Class cl) { From 3080d249b1e5c762618784a0b7a88128d7799fc8 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Wed, 20 Dec 2023 17:04:32 +0100 Subject: [PATCH 02/23] performance improvements --- .../java/lang/reflect/ProxyGenerator.java | 288 ++++++++++++------ 1 file changed, 191 insertions(+), 97 deletions(-) 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 d69b4ce4c6387..2685907e561a0 100644 --- a/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java +++ b/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java @@ -45,6 +45,9 @@ import java.util.Objects; import static java.lang.classfile.ClassFile.*; +import java.lang.classfile.attribute.StackMapFrameInfo; +import java.lang.classfile.attribute.StackMapTableAttribute; +import java.util.StringJoiner; /** * ProxyGenerator contains the code to generate a dynamic proxy class @@ -54,7 +57,6 @@ * "generateProxyClass" method. */ final class ProxyGenerator { - private static final int CLASSFILE_VERSION = ClassFileFormatVersion.latest().major(); private static final ClassDesc CD_ClassLoader = ClassDesc.ofInternalName("java/lang/ClassLoader"), @@ -105,25 +107,101 @@ final class ProxyGenerator { private static final ProxyMethod equalsMethod; private static final ProxyMethod toStringMethod; + private static final ClassModel TEMPLATE; + + private static final Utf8Entry UE_Method; + private static final MethodRefEntry MRE_Class_getClassLoader; + private static final MethodRefEntry MRE_NoSuchMethodError_init; + private static final ClassEntry CE_NoSuchMethodError; + private static final FieldRefEntry FRE_Proxy_h; + private static final ClassEntry CE_NoClassDefFoundError; + private static final MethodRefEntry MRE_NoClassDefFoundError_init; + private static final MethodRefEntry MRE_Throwable_getMessage; + private static final MethodRefEntry MRE_Class_forName; + private static final ClassEntry CE_Class; + private static final MethodRefEntry MRE_Class_getMethod; + private static final ClassEntry CE_Object; + private static final InterfaceMethodRefEntry IMRE_InvocationHandler_invoke; + private static final MethodRefEntry MRE_UndeclaredThrowableException_init; + private static final ClassEntry CE_UndeclaredThrowableException; + private static final ClassEntry CE_Throwable; + private static final List THROWABLE_STACK; + static { + var cc = ClassFile.of(); + var entries = new ArrayList(20); + var q = new Object() { + PoolEntry[] entries; + int i; + @SuppressWarnings("unchecked") + T next() { + return (T) TEMPLATE.constantPool().entryByIndex(entries[i++].index()); + } + }; + TEMPLATE = cc.parse(cc.build(CD_Proxy, clb -> { + clb.withSuperclass(CD_Proxy); + generateConstructor(clb); + generateLookupAccessor(clb); + var cp = clb.constantPool(); + q.entries = new PoolEntry[] { + cp.utf8Entry("m0"), + cp.utf8Entry("m1"), + cp.utf8Entry("m2"), + cp.utf8Entry(CD_Method), + cp.methodRefEntry(CD_Class, "getClassLoader", MTD_ClassLoader), + cp.classEntry(CD_NoSuchMethodError), + cp.methodRefEntry(CD_NoSuchMethodError, INIT_NAME, MTD_void_String), + cp.fieldRefEntry(CD_Proxy, handlerFieldName, CD_InvocationHandler), + cp.classEntry(CD_NoClassDefFoundError), + cp.methodRefEntry(CD_NoClassDefFoundError, INIT_NAME, MTD_void_String), + cp.methodRefEntry(CD_Throwable, "getMessage", MTD_String), + cp.methodRefEntry(CD_Class, "forName", MTD_Class_String_boolean_ClassLoader), + cp.classEntry(CD_Class), + cp.methodRefEntry(CD_Class, "getMethod", MTD_Method_String_ClassArray), + cp.classEntry(CD_Object), + cp.interfaceMethodRefEntry(CD_InvocationHandler, "invoke", MTD_Object_Object_Method_ObjectArray), + cp.methodRefEntry(CD_UndeclaredThrowableException, INIT_NAME, MTD_void_Throwable), + cp.classEntry(CD_UndeclaredThrowableException), + cp.classEntry(CD_Throwable) + }; + })); + try { - hashCodeMethod = new ProxyMethod(Object.class.getMethod("hashCode"), "m0"); - equalsMethod = new ProxyMethod(Object.class.getMethod("equals", Object.class), "m1"); - toStringMethod = new ProxyMethod(Object.class.getMethod("toString"), "m2"); + hashCodeMethod = new ProxyMethod(Object.class.getMethod("hashCode"), q.next()); + equalsMethod = new ProxyMethod(Object.class.getMethod("equals", Object.class), q.next()); + toStringMethod = new ProxyMethod(Object.class.getMethod("toString"), q.next()); } catch (NoSuchMethodException e) { throw new NoSuchMethodError(e.getMessage()); } + UE_Method = q.next(); + MRE_Class_getClassLoader = q.next(); + CE_NoSuchMethodError = q.next(); + MRE_NoSuchMethodError_init = q.next(); + FRE_Proxy_h = q.next(); + CE_NoClassDefFoundError = q.next(); + MRE_NoClassDefFoundError_init = q.next(); + MRE_Throwable_getMessage = q.next(); + MRE_Class_forName = q.next(); + CE_Class = q.next(); + MRE_Class_getMethod = q.next(); + CE_Object = q.next(); + IMRE_InvocationHandler_invoke = q.next(); + MRE_UndeclaredThrowableException_init = q.next(); + CE_UndeclaredThrowableException = q.next(); + CE_Throwable = q.next(); + THROWABLE_STACK = List.of(StackMapFrameInfo.ObjectVerificationTypeInfo.of(CE_Throwable)); } /** * Classfile context */ private final ClassFile classfileContext; + private final ConstantPoolBuilder cp; /** * Name of proxy class */ - private final ClassDesc classDesc; + private ClassEntry classEntry; /** * Proxy interfaces @@ -158,9 +236,11 @@ 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.classDesc = ClassDesc.of(className); + this.cp = ConstantPoolBuilder.of(TEMPLATE); + this.classEntry = cp.classEntry(ClassDesc.of(className)); this.interfaces = interfaces; this.accessFlags = accessFlags; } @@ -411,56 +491,52 @@ private static void collectCompatibleTypes(Class[] from, * class file generation process. */ private byte[] generateClassFile() { - return classfileContext.build(classDesc, clb -> { - clb.withFlags(accessFlags); - clb.withSuperclass(CD_Proxy); - clb.withInterfaceSymbols(interfaces.stream().map(ProxyGenerator::toClassDesc).toList()); - clb.withVersion(CLASSFILE_VERSION, 0); - - /* - * Add proxy methods for the hashCode, equals, - * and toString methods of java.lang.Object. This is done before - * the methods from the proxy interfaces so that the methods from - * java.lang.Object take precedence over duplicate methods in the - * proxy interfaces. - */ - addProxyMethod(hashCodeMethod); - addProxyMethod(equalsMethod); - addProxyMethod(toStringMethod); + /* + * Add proxy methods for the hashCode, equals, + * and toString methods of java.lang.Object. This is done before + * the methods from the proxy interfaces so that the methods from + * java.lang.Object take precedence over duplicate methods in the + * proxy interfaces. + */ + addProxyMethod(hashCodeMethod); + addProxyMethod(equalsMethod); + addProxyMethod(toStringMethod); - /* - * Accumulate all of the methods from the proxy interfaces. - */ - for (Class intf : interfaces) { - for (Method m : intf.getMethods()) { - if (!Modifier.isStatic(m.getModifiers())) { - addProxyMethod(m, intf); - } + /* + * Accumulate all of the methods from the proxy interfaces. + */ + for (Class intf : interfaces) { + for (Method m : intf.getMethods()) { + if (!Modifier.isStatic(m.getModifiers())) { + addProxyMethod(m, intf, cp); } } + } - /* - * For each set of proxy methods with the same signature, - * verify that the methods' return types are compatible. - */ - for (List sigmethods : proxyMethods.values()) { - checkReturnTypes(sigmethods); - } + /* + * For each set of proxy methods with the same signature, + * verify that the methods' return types are compatible. + */ + for (List sigmethods : proxyMethods.values()) { + checkReturnTypes(sigmethods); + } - generateConstructor(clb); + return classfileContext.build(classEntry, cp, clb -> { + TEMPLATE.forEach(clb); + clb.withFlags(accessFlags); + clb.withInterfaceSymbols(interfaces.stream().map(ProxyGenerator::toClassDesc).toList()); for (List sigmethods : proxyMethods.values()) { for (ProxyMethod pm : sigmethods) { // add static field for the Method object - clb.withField(pm.methodFieldName, CD_Method, ACC_PRIVATE | ACC_STATIC | ACC_FINAL); + clb.withField(pm.methodFieldName, UE_Method, ACC_PRIVATE | ACC_STATIC | ACC_FINAL); // Generate code for proxy method - pm.generateMethod(clb, classDesc); + pm.generateMethod(clb, classEntry); } } generateStaticInitializer(clb); - generateLookupAccessor(clb); }); } @@ -477,7 +553,7 @@ private byte[] generateClassFile() { * passed to the invocation handler's "invoke" method for a given * set of duplicate methods. */ - private void addProxyMethod(Method m, Class fromClass) { + private void addProxyMethod(Method m, Class fromClass, ConstantPoolBuilder cp) { Class returnType = m.getReturnType(); Class[] exceptionTypes = m.getSharedExceptionTypes(); @@ -503,7 +579,7 @@ private void addProxyMethod(Method m, Class fromClass) { } sigmethods.add(new ProxyMethod(m, sig, m.getSharedParameterTypes(), returnType, exceptionTypes, fromClass, - "m" + proxyMethodCount++)); + cp.utf8Entry("m" + proxyMethodCount++))); } /** @@ -521,7 +597,7 @@ private void addProxyMethod(ProxyMethod pm) { /** * Generate the constructor method for the proxy class. */ - private void generateConstructor(ClassBuilder clb) { + private static void generateConstructor(ClassBuilder clb) { clb.withMethodBody(INIT_NAME, MTD_void_InvocationHandler, ACC_PUBLIC, cob -> cob .aload(cob.receiverSlot()) .aload(cob.parameterSlot(0)) @@ -536,31 +612,39 @@ private void generateStaticInitializer(ClassBuilder clb) { clb.withMethodBody(CLASS_INIT_NAME, MTD_void, ACC_STATIC, cob -> cob // Put ClassLoader at local variable index 0, used by // Class.forName(String, boolean, ClassLoader) calls - .constantInstruction(classDesc) - .invokevirtual(CD_Class, "getClassLoader", MTD_ClassLoader) + .ldc(classEntry) + .invokevirtual(MRE_Class_getClassLoader) .astore(0) .trying(tryb -> { for (List sigmethods : proxyMethods.values()) { for (ProxyMethod pm : sigmethods) { - pm.codeFieldInitialization(tryb, classDesc); + pm.codeFieldInitialization(tryb, classEntry); } } tryb.return_(); - }, cb -> cb - .catching(CD_NoSuchMethodException, nsmb -> nsmb - .new_(CD_NoSuchMethodError) + }, cb -> { + var c1 = cob.newLabel(); + cb.catching(CD_NoSuchMethodException, nsmb -> nsmb + .labelBinding(c1) + .new_(CE_NoSuchMethodError) .dup_x1() .swap() - .invokevirtual(CD_Throwable, "getMessage", MTD_String) - .invokespecial(CD_NoSuchMethodError, INIT_NAME, MTD_void_String) - .athrow()) - .catching(CD_ClassNotFoundException, cnfb -> cnfb - .new_(CD_NoClassDefFoundError) + .invokevirtual(MRE_Throwable_getMessage) + .invokespecial(MRE_NoSuchMethodError_init) + .athrow()); + var c2 = cob.newLabel(); + cb.catching(CD_ClassNotFoundException, cnfb -> cnfb + .labelBinding(c2) + .new_(CE_NoClassDefFoundError) .dup_x1() .swap() - .invokevirtual(CD_Throwable, "getMessage", MTD_String) - .invokespecial(CD_NoClassDefFoundError, INIT_NAME, MTD_void_String) - .athrow()))); + .invokevirtual(MRE_Throwable_getMessage) + .invokespecial(MRE_NoClassDefFoundError_init) + .athrow()); + cob.with(StackMapTableAttribute.of(List.of( + StackMapFrameInfo.of(c1, List.of(), THROWABLE_STACK), + StackMapFrameInfo.of(c2, List.of(), THROWABLE_STACK)))); + })); } /** @@ -568,7 +652,7 @@ private void generateStaticInitializer(ClassBuilder clb) { * on this proxy class if the caller's lookup class is java.lang.reflect.Proxy; * otherwise, IllegalAccessException is thrown */ - private void generateLookupAccessor(ClassBuilder clb) { + private static void generateLookupAccessor(ClassBuilder clb) { clb.withMethod(NAME_LOOKUP_ACCESSOR, MTD_MethodHandles$Lookup_MethodHandles$Lookup, ACC_PRIVATE | ACC_STATIC, @@ -604,12 +688,12 @@ private static class ProxyMethod { private final Class fromClass; private final Class[] parameterTypes; private final Class returnType; - private final String methodFieldName; + private final Utf8Entry methodFieldName; private Class[] exceptionTypes; private ProxyMethod(Method method, String sig, Class[] parameterTypes, Class returnType, Class[] exceptionTypes, - Class fromClass, String methodFieldName) { + Class fromClass, Utf8Entry methodFieldName) { this.method = method; this.shortSignature = sig; this.parameterTypes = parameterTypes; @@ -625,7 +709,7 @@ private ProxyMethod(Method method, String sig, Class[] parameterTypes, * @param method The method for which to create a proxy * @param methodFieldName the fieldName to generate */ - private ProxyMethod(Method method, String methodFieldName) { + private ProxyMethod(Method method, Utf8Entry methodFieldName) { this(method, method.toShortSignature(), method.getSharedParameterTypes(), method.getReturnType(), method.getSharedExceptionTypes(), method.getDeclaringClass(), methodFieldName); @@ -634,31 +718,33 @@ private ProxyMethod(Method method, String methodFieldName) { /** * Generate this method, including the code and exception table entry. */ - private void generateMethod(ClassBuilder clb, ClassDesc className) { - MethodTypeDesc desc = MethodTypeDesc.of(toClassDesc(returnType), - Arrays.stream(parameterTypes).map(ProxyGenerator::toClassDesc).toArray(ClassDesc[]::new)); + private void generateMethod(ClassBuilder clb, ClassEntry className) { + var cp = clb.constantPool(); + var desc = new StringJoiner("", "(", ")" + returnType.descriptorString()); + for (var pt : parameterTypes) { + desc.add(pt.descriptorString()); + } 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 -> { - ConstantPoolBuilder cpb = mb.constantPool(); + clb.withMethod(cp.utf8Entry(method.getName()), cp.utf8Entry(desc.toString()), accessFlags, mb -> { List exceptionClassEntries = Arrays.asList(exceptionTypes) .stream() .map(ProxyGenerator::toClassDesc) - .map(cpb::classEntry) + .map(cp::classEntry) .toList(); mb.with(ExceptionsAttribute.of(exceptionClassEntries)); mb.withCode(cob -> cob.trying(tryb -> { tryb.aload(tryb.receiverSlot()) - .getfield(CD_Proxy, handlerFieldName, CD_InvocationHandler) + .getfield(FRE_Proxy_h) .aload(tryb.receiverSlot()) - .getstatic(className, methodFieldName, CD_Method); + .getstatic(cp.fieldRefEntry(className, cp.nameAndTypeEntry(methodFieldName, UE_Method))); if (parameterTypes.length > 0) { // Create an array and fill with the parameters converting primitives to wrappers tryb.constantInstruction(parameterTypes.length) - .anewarray(CD_Object); + .anewarray(CE_Object); for (int i = 0; i < parameterTypes.length; i++) { tryb.dup() .constantInstruction(i); @@ -669,7 +755,7 @@ private void generateMethod(ClassBuilder clb, ClassDesc className) { tryb.aconst_null(); } - tryb.invokeinterface(CD_InvocationHandler, "invoke", MTD_Object_Object_Method_ObjectArray); + tryb.invokeinterface(IMRE_InvocationHandler_invoke); if (returnType == void.class) { tryb.pop() @@ -679,15 +765,21 @@ private void generateMethod(ClassBuilder clb, ClassDesc className) { } }, catchBuilder -> { if (!catchList.isEmpty()) { + var c1 = cob.newLabel(); catchBuilder.catchingMulti(catchList, ehb -> ehb + .labelBinding(c1) .athrow()); // just rethrow the exception - + var c2 = cob.newLabel(); catchBuilder.catching(CD_Throwable, ehb -> ehb - .new_(CD_UndeclaredThrowableException) + .labelBinding(c2) + .new_(CE_UndeclaredThrowableException) .dup_x1() .swap() - .invokespecial(CD_UndeclaredThrowableException, INIT_NAME, MTD_void_Throwable) + .invokespecial(MRE_UndeclaredThrowableException_init) .athrow()); + cob.with(StackMapTableAttribute.of(List.of( + StackMapFrameInfo.of(c1, List.of(), THROWABLE_STACK), + StackMapFrameInfo.of(c2, List.of(), THROWABLE_STACK)))); } })); }); @@ -703,7 +795,7 @@ private void codeWrapArgument(CodeBuilder cob, Class type, int slot) { if (type.isPrimitive()) { cob.loadInstruction(TypeKind.from(type).asLoadable(), slot); PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(type); - cob.invokestatic(prim.wrapperClass, "valueOf", prim.wrapperValueOf); + cob.invokestatic(prim.wrapperMethodRef); } else { cob.aload(slot); } @@ -718,8 +810,8 @@ private void codeUnwrapReturnValue(CodeBuilder cob, Class type) { if (type.isPrimitive()) { PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(type); - cob.typeCheckInstruction(Opcode.CHECKCAST, prim.wrapperClass) - .invokevirtual(prim.wrapperClass, prim.unwrapMethodName, prim.unwrapMethodType) + cob.checkcast(prim.wrapperClass) + .invokevirtual(prim.unwrapMethodRef) .returnInstruction(TypeKind.from(type).asLoadable()); } else { cob.checkcast(toClassDesc(type)) @@ -732,12 +824,13 @@ private void codeUnwrapReturnValue(CodeBuilder cob, Class type) { * the Method object for this proxy method. A class loader is * anticipated at local variable index 0. */ - private void codeFieldInitialization(CodeBuilder cob, ClassDesc className) { + private void codeFieldInitialization(CodeBuilder cob, ClassEntry className) { + var cp = cob.constantPool(); codeClassForName(cob, fromClass); cob.ldc(method.getName()) .constantInstruction(parameterTypes.length) - .anewarray(CD_Class); + .anewarray(CE_Class); // Construct an array with the parameter types mapping primitives to Wrapper types for (int i = 0; i < parameterTypes.length; i++) { @@ -745,15 +838,15 @@ private void codeFieldInitialization(CodeBuilder cob, ClassDesc className) { .constantInstruction(i); if (parameterTypes[i].isPrimitive()) { PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(parameterTypes[i]); - cob.getstatic(prim.wrapperClass, "TYPE", CD_Class); + cob.getstatic(prim.typeFieldRef); } else { codeClassForName(cob, parameterTypes[i]); } cob.aastore(); } // lookup the method - cob.invokevirtual(CD_Class, "getMethod", MTD_Method_String_ClassArray) - .putstatic(className, methodFieldName, CD_Method); + cob.invokevirtual(MRE_Class_getMethod) + .putstatic(cp.fieldRefEntry(className, cp.nameAndTypeEntry(methodFieldName, UE_Method))); } /* @@ -768,10 +861,10 @@ private void codeFieldInitialization(CodeBuilder cob, ClassDesc className) { * loader is anticipated at local variable index 0. */ private void codeClassForName(CodeBuilder cob, Class cl) { - cob.constantInstruction(Opcode.LDC, cl.getName()) + cob.ldc(cl.getName()) .iconst_0() // false .aload(0)// classLoader - .invokestatic(CD_Class, "forName", MTD_Class_String_boolean_ClassLoader); + .invokestatic(MRE_Class_forName); } @Override @@ -780,6 +873,7 @@ public String toString() { } } + 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 @@ -796,28 +890,28 @@ private enum PrimitiveTypeInfo { BOOLEAN(boolean.class, CD_boolean, CD_Boolean); /** - * internal name of corresponding wrapper class + * CP entry of corresponding wrapper class */ - private final ClassDesc wrapperClass; + private final ClassEntry wrapperClass; /** - * method descriptor for wrapper class "valueOf" factory method + * CP entry for wrapper class "valueOf" factory method */ - private final MethodTypeDesc wrapperValueOf; + private final MethodRefEntry wrapperMethodRef; /** - * name of wrapper class method for retrieving primitive value + * CP entry of wrapper class method for retrieving primitive value */ - private final String unwrapMethodName; + private final MethodRefEntry unwrapMethodRef; /** - * descriptor of same method + * CP entry of wrapper class TYPE field */ - private final MethodTypeDesc unwrapMethodType; + private final FieldRefEntry typeFieldRef; PrimitiveTypeInfo(Class primitiveClass, ClassDesc baseType, ClassDesc wrapperClass) { assert baseType.isPrimitive(); - this.wrapperClass = wrapperClass; - this.wrapperValueOf = MethodTypeDesc.of(wrapperClass, baseType); - this.unwrapMethodName = primitiveClass.getName() + "Value"; - this.unwrapMethodType = MethodTypeDesc.of(baseType); + 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); } public static PrimitiveTypeInfo get(Class cl) { From 0dc63d4edf40fd9458fbfa0c7661d57ed0022981 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Wed, 20 Dec 2023 19:12:03 +0100 Subject: [PATCH 03/23] StackCounter performance boost --- .../internal/classfile/impl/StackCounter.java | 98 +++++++++++++------ 1 file changed, 68 insertions(+), 30 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java b/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java index d171bdbe10f6b..db524932945be 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java @@ -25,28 +25,28 @@ */ package jdk.internal.classfile.impl; -import java.lang.constant.ClassDesc; -import java.lang.constant.MethodTypeDesc; import java.nio.ByteBuffer; import java.util.BitSet; -import java.util.LinkedHashMap; import java.util.List; -import java.util.stream.Collectors; import java.lang.classfile.TypeKind; import java.lang.classfile.constantpool.ConstantDynamicEntry; import java.lang.classfile.constantpool.DynamicConstantPoolEntry; import java.lang.classfile.constantpool.MemberRefEntry; import static java.lang.classfile.ClassFile.*; +import java.lang.classfile.constantpool.Utf8Entry; +import java.lang.constant.MethodTypeDesc; +import java.util.LinkedList; public final class StackCounter { + private record Target(int bci, int stack) {} + static StackCounter of(DirectCodeBuilder dcb, BufWriterImpl buf) { return new StackCounter( dcb, - buf.thisClass().asSymbol(), - dcb.methodInfo.methodName().stringValue(), - dcb.methodInfo.methodTypeSymbol(), + dcb.methodInfo.methodName(), + dcb.methodInfo.methodType(), (dcb.methodInfo.methodFlags() & ACC_STATIC) != 0, dcb.bytecodesBufWriter.asByteBuffer().slice(0, dcb.bytecodesBufWriter.size()), dcb.constantPool, @@ -56,15 +56,15 @@ static StackCounter of(DirectCodeBuilder dcb, BufWriterImpl buf) { private int stack, maxStack, maxLocals, rets; private final RawBytecodeHelper bcs; - private final String methodName; - private final MethodTypeDesc methodDesc; + private final Utf8Entry methodName; + private final Utf8Entry methodDesc; private final SplitConstantPool cp; - private final LinkedHashMap map; + private final LinkedList targets; private final BitSet visited; private void jump(int targetBci) { if (!visited.get(targetBci)) { - map.put(targetBci, stack); + targets.add(new Target(targetBci, stack)); } } @@ -78,13 +78,13 @@ private void ensureLocalSlot(int index) { } private boolean next() { - var it = map.entrySet().iterator(); + var it = targets.iterator(); while (it.hasNext()) { var en = it.next(); it.remove(); - if (!visited.get(en.getKey())) { - bcs.nextBci = en.getKey(); - stack = en.getValue(); + if (!visited.get(en.bci)) { + bcs.nextBci = en.bci; + stack = en.stack; return true; } } @@ -93,9 +93,8 @@ private boolean next() { } public StackCounter(LabelContext labelContext, - ClassDesc thisClass, - String methodName, - MethodTypeDesc methodDesc, + Utf8Entry methodName, + Utf8Entry methodDesc, boolean isStatic, ByteBuffer bytecode, SplitConstantPool cp, @@ -103,16 +102,14 @@ public StackCounter(LabelContext labelContext, this.methodName = methodName; this.methodDesc = methodDesc; this.cp = cp; - map = new LinkedHashMap<>(); + targets = new LinkedList<>(); maxStack = stack = rets = 0; - for (var h : handlers) map.put(labelContext.labelToBci(h.handler), 1); + for (var h : handlers) targets.add(new Target(labelContext.labelToBci(h.handler), 1)); maxLocals = isStatic ? 0 : 1; - for (var cd : methodDesc.parameterList()) { - maxLocals += Util.slotSize(cd); - } + maxLocals += countMethodStack(methodDesc, false); bcs = new RawBytecodeHelper(bytecode); visited = new BitSet(bcs.endBci); - map.put(0, 0); + targets.add(new Target(0, 0)); while (next()) { while (!bcs.isLastBytecode()) { bcs.rawNext(); @@ -307,14 +304,10 @@ public StackCounter(LabelContext labelContext, case INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC, INVOKEINTERFACE, INVOKEDYNAMIC -> { var cpe = cp.entryByIndex(bcs.getIndexU2()); var nameAndType = opcode == INVOKEDYNAMIC ? ((DynamicConstantPoolEntry)cpe).nameAndType() : ((MemberRefEntry)cpe).nameAndType(); - var mDesc = MethodTypeDesc.ofDescriptor(nameAndType.type().stringValue()); - for (var arg : mDesc.parameterList()) { - addStackSlot(-TypeKind.from(arg).slotSize()); - } + addStackSlot(-countMethodStack(nameAndType.type(), true)); if (opcode != INVOKESTATIC && opcode != INVOKEDYNAMIC) { addStackSlot(-1); } - addStackSlot(TypeKind.from(mDesc.returnType()).slotSize()); } case MULTIANEWARRAY -> addStackSlot (1 - bcs.getU1(bcs.bci + 3)); @@ -343,6 +336,51 @@ public StackCounter(LabelContext labelContext, maxStack += rets * maxStack; } + private static int countMethodStack(Utf8Entry descriptor, boolean subReturn) { + int cur = 0, end = descriptor.length(); + if (cur >= end || descriptor.charAt(cur) != '(') + throw new IllegalArgumentException("Bad method descriptor: " + descriptor); + ++cur; // skip '(' + int count = 0; + boolean inArray = false; + while (cur < end) { + switch (descriptor.charAt(cur++)) { + case 'Z', 'B', 'C', 'S', 'I', 'F' -> { + count++; + inArray = false; + } + case 'J', 'D' -> { + count += inArray ? 1 : 2; + inArray = false; + } + case 'L' -> { + while (cur < end && descriptor.charAt(cur++) != ';'); + count++; + inArray = false; + } + case '[' -> { + inArray = true; + } + case ')' -> { + if (cur < end) { + if (subReturn) { + return switch (descriptor.charAt(cur++)) { + case 'Z', 'B', 'C', 'S', 'I', 'F', '[', 'L' -> count - 1; + case 'J', 'D' -> count - 2; + case 'V' -> count; + default -> throw new IllegalArgumentException("Bad method descriptor: " + descriptor); + }; + } else { + return count; + } + } + } + default -> throw new IllegalArgumentException("Bad method descriptor: " + descriptor); + } + } + throw new IllegalArgumentException("Bad method descriptor: " + descriptor); + } + /** * Calculated maximum number of the locals required * @return maximum number of the locals required @@ -377,6 +415,6 @@ private void error(String msg) { msg, bcs.bci, methodName, - methodDesc.displayDescriptor())); + MethodTypeDesc.ofDescriptor(methodDesc.stringValue()).displayDescriptor())); } } From a21fb26413112aebb7a98137ec11a938e5dd8846 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Wed, 20 Dec 2023 20:58:01 +0100 Subject: [PATCH 04/23] performance improvements --- .../java/lang/reflect/ProxyGenerator.java | 161 +++++++++--------- 1 file changed, 81 insertions(+), 80 deletions(-) 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 2685907e561a0..bc8d08228248b 100644 --- a/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java +++ b/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java @@ -125,6 +125,8 @@ final class ProxyGenerator { private static final MethodRefEntry MRE_UndeclaredThrowableException_init; private static final ClassEntry CE_UndeclaredThrowableException; private static final ClassEntry CE_Throwable; + private static final ClassEntry CE_NoSuchMethodException; + private static final ClassEntry CE_ClassNotFoundException; private static final List THROWABLE_STACK; static { @@ -162,6 +164,8 @@ T next() { cp.interfaceMethodRefEntry(CD_InvocationHandler, "invoke", MTD_Object_Object_Method_ObjectArray), cp.methodRefEntry(CD_UndeclaredThrowableException, INIT_NAME, MTD_void_Throwable), cp.classEntry(CD_UndeclaredThrowableException), + cp.classEntry(CD_NoSuchMethodException), + cp.classEntry(CD_ClassNotFoundException), cp.classEntry(CD_Throwable) }; })); @@ -188,6 +192,8 @@ T next() { IMRE_InvocationHandler_invoke = q.next(); MRE_UndeclaredThrowableException_init = q.next(); CE_UndeclaredThrowableException = q.next(); + CE_NoSuchMethodException = q.next(); + CE_ClassNotFoundException = q.next(); CE_Throwable = q.next(); THROWABLE_STACK = List.of(StackMapFrameInfo.ObjectVerificationTypeInfo.of(CE_Throwable)); } @@ -609,42 +615,39 @@ private static void generateConstructor(ClassBuilder clb) { * Generate the static initializer method for the proxy class. */ private void generateStaticInitializer(ClassBuilder clb) { - clb.withMethodBody(CLASS_INIT_NAME, MTD_void, ACC_STATIC, cob -> cob - // Put ClassLoader at local variable index 0, used by - // Class.forName(String, boolean, ClassLoader) calls - .ldc(classEntry) - .invokevirtual(MRE_Class_getClassLoader) - .astore(0) - .trying(tryb -> { - for (List sigmethods : proxyMethods.values()) { - for (ProxyMethod pm : sigmethods) { - pm.codeFieldInitialization(tryb, classEntry); - } - } - tryb.return_(); - }, cb -> { - var c1 = cob.newLabel(); - cb.catching(CD_NoSuchMethodException, nsmb -> nsmb - .labelBinding(c1) - .new_(CE_NoSuchMethodError) - .dup_x1() - .swap() - .invokevirtual(MRE_Throwable_getMessage) - .invokespecial(MRE_NoSuchMethodError_init) - .athrow()); - var c2 = cob.newLabel(); - cb.catching(CD_ClassNotFoundException, cnfb -> cnfb - .labelBinding(c2) - .new_(CE_NoClassDefFoundError) - .dup_x1() - .swap() - .invokevirtual(MRE_Throwable_getMessage) - .invokespecial(MRE_NoClassDefFoundError_init) - .athrow()); - cob.with(StackMapTableAttribute.of(List.of( - StackMapFrameInfo.of(c1, List.of(), THROWABLE_STACK), - StackMapFrameInfo.of(c2, List.of(), THROWABLE_STACK)))); - })); + 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) + .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) + .athrow() + .with(StackMapTableAttribute.of(List.of( + StackMapFrameInfo.of(c1, List.of(), THROWABLE_STACK), + StackMapFrameInfo.of(c2, List.of(), THROWABLE_STACK)))); + }); } /** @@ -734,54 +737,52 @@ private void generateMethod(ClassBuilder clb, ClassEntry className) { .map(cp::classEntry) .toList(); mb.with(ExceptionsAttribute.of(exceptionClassEntries)); - mb.withCode(cob -> - cob.trying(tryb -> { - tryb.aload(tryb.receiverSlot()) - .getfield(FRE_Proxy_h) - .aload(tryb.receiverSlot()) - .getstatic(cp.fieldRefEntry(className, cp.nameAndTypeEntry(methodFieldName, UE_Method))); - - if (parameterTypes.length > 0) { - // Create an array and fill with the parameters converting primitives to wrappers - tryb.constantInstruction(parameterTypes.length) - .anewarray(CE_Object); - for (int i = 0; i < parameterTypes.length; i++) { - tryb.dup() - .constantInstruction(i); - codeWrapArgument(tryb, parameterTypes[i], tryb.parameterSlot(i)); - tryb.aastore(); - } - } else { - tryb.aconst_null(); + mb.withCode(cob -> { + cob.aload(cob.receiverSlot()) + .getfield(FRE_Proxy_h) + .aload(cob.receiverSlot()) + .getstatic(cp.fieldRefEntry(className, cp.nameAndTypeEntry(methodFieldName, UE_Method))); + + if (parameterTypes.length > 0) { + // Create an array and fill with the parameters converting primitives to wrappers + cob.constantInstruction(parameterTypes.length) + .anewarray(CE_Object); + for (int i = 0; i < parameterTypes.length; i++) { + cob.dup() + .constantInstruction(i); + codeWrapArgument(cob, parameterTypes[i], cob.parameterSlot(i)); + cob.aastore(); } + } else { + cob.aconst_null(); + } - tryb.invokeinterface(IMRE_InvocationHandler_invoke); + cob.invokeinterface(IMRE_InvocationHandler_invoke); - if (returnType == void.class) { - tryb.pop() - .return_(); - } else { - codeUnwrapReturnValue(tryb, returnType); - } - }, catchBuilder -> { - if (!catchList.isEmpty()) { - var c1 = cob.newLabel(); - catchBuilder.catchingMulti(catchList, ehb -> ehb - .labelBinding(c1) - .athrow()); // just rethrow the exception - var c2 = cob.newLabel(); - catchBuilder.catching(CD_Throwable, ehb -> ehb - .labelBinding(c2) - .new_(CE_UndeclaredThrowableException) - .dup_x1() - .swap() - .invokespecial(MRE_UndeclaredThrowableException_init) - .athrow()); - cob.with(StackMapTableAttribute.of(List.of( - StackMapFrameInfo.of(c1, List.of(), THROWABLE_STACK), - StackMapFrameInfo.of(c2, List.of(), THROWABLE_STACK)))); + if (returnType == void.class) { + cob.pop() + .return_(); + } else { + codeUnwrapReturnValue(cob, returnType); + } + if (!catchList.isEmpty()) { + var c1 = cob.newBoundLabel(); + for (var exc : catchList) { + cob.exceptionCatch(cob.startLabel(), c1, c1, exc); } - })); + cob.athrow(); // just rethrow the exception + var c2 = cob.newBoundLabel(); + cob.exceptionCatchAll(cob.startLabel(), c1, c2) + .new_(CE_UndeclaredThrowableException) + .dup_x1() + .swap() + .invokespecial(MRE_UndeclaredThrowableException_init) + .athrow() + .with(StackMapTableAttribute.of(List.of( + StackMapFrameInfo.of(c1, List.of(), THROWABLE_STACK), + StackMapFrameInfo.of(c2, List.of(), THROWABLE_STACK)))); + } + }); }); } From b7a60ae944983224e3b4c097576c496351394fe0 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Thu, 21 Dec 2023 01:21:01 +0100 Subject: [PATCH 05/23] SplitConstantPool performance fix --- .../jdk/internal/classfile/impl/SplitConstantPool.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/SplitConstantPool.java b/src/java.base/share/classes/jdk/internal/classfile/impl/SplitConstantPool.java index eb5aab90bb016..db2b4f030a327 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/SplitConstantPool.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/SplitConstantPool.java @@ -87,6 +87,7 @@ public SplitConstantPool(ClassReader parent) { this.bsmSize = parentBsmSize; this.myEntries = new PoolEntry[8]; this.myBsmEntries = new BootstrapMethodEntryImpl[8]; + this.doneFullScan = true; } @Override @@ -189,10 +190,15 @@ protected PoolEntry fetchElement(int index) { // So we inflate the map with whatever we've got from the parent, and // later, if we miss, we do a one-time full inflation before creating // a new entry. - for (int i=1; i Date: Thu, 21 Dec 2023 02:08:17 +0100 Subject: [PATCH 06/23] performance improvements --- .../java/lang/reflect/ProxyGenerator.java | 111 +++++++++--------- 1 file changed, 58 insertions(+), 53 deletions(-) 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 bc8d08228248b..f6d822108565b 100644 --- a/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java +++ b/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java @@ -294,6 +294,17 @@ public Void run() { return classFile; } + /** + * {@return the entries of the given type} + * @param type the {@code Class} objects, no primitives nor arrays + */ + 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('.', '/'))); + return ces; + } + /** * {@return the {@code ClassDesc} of the given type} * @param type the {@code Class} object @@ -417,7 +428,7 @@ private static void checkReturnTypes(List methods) { * given list of declared exceptions, indicating that no exceptions * need to be caught. */ - private static List computeUniqueCatchList(Class[] exceptions) { + private static List> computeUniqueCatchList(Class[] exceptions) { List> uniqueList = new ArrayList<>(); // unique exceptions to catch @@ -465,7 +476,7 @@ private static List computeUniqueCatchList(Class[] exceptions) { // This exception is unique (so far): add it to the list to catch. uniqueList.add(ex); } - return uniqueList.stream().map(ProxyGenerator::toClassDesc).toList(); + return uniqueList; } /** @@ -530,7 +541,7 @@ private byte[] generateClassFile() { return classfileContext.build(classEntry, cp, clb -> { TEMPLATE.forEach(clb); clb.withFlags(accessFlags); - clb.withInterfaceSymbols(interfaces.stream().map(ProxyGenerator::toClassDesc).toList()); + clb.withInterfaces(toClassEntries(cp, interfaces)); for (List sigmethods : proxyMethods.values()) { for (ProxyMethod pm : sigmethods) { @@ -730,60 +741,54 @@ private void generateMethod(ClassBuilder clb, ClassEntry className) { int accessFlags = (method.isVarArgs()) ? ACC_VARARGS | ACC_PUBLIC | ACC_FINAL : ACC_PUBLIC | ACC_FINAL; var catchList = computeUniqueCatchList(exceptionTypes); - clb.withMethod(cp.utf8Entry(method.getName()), cp.utf8Entry(desc.toString()), accessFlags, mb -> { - List exceptionClassEntries = Arrays.asList(exceptionTypes) - .stream() - .map(ProxyGenerator::toClassDesc) - .map(cp::classEntry) - .toList(); - mb.with(ExceptionsAttribute.of(exceptionClassEntries)); - mb.withCode(cob -> { - cob.aload(cob.receiverSlot()) - .getfield(FRE_Proxy_h) - .aload(cob.receiverSlot()) - .getstatic(cp.fieldRefEntry(className, cp.nameAndTypeEntry(methodFieldName, UE_Method))); - - if (parameterTypes.length > 0) { - // Create an array and fill with the parameters converting primitives to wrappers - cob.constantInstruction(parameterTypes.length) - .anewarray(CE_Object); - for (int i = 0; i < parameterTypes.length; i++) { - cob.dup() - .constantInstruction(i); - codeWrapArgument(cob, parameterTypes[i], cob.parameterSlot(i)); - cob.aastore(); + clb.withMethod(cp.utf8Entry(method.getName()), cp.utf8Entry(desc.toString()), 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))); + + if (parameterTypes.length > 0) { + // Create an array and fill with the parameters converting primitives to wrappers + cob.constantInstruction(parameterTypes.length) + .anewarray(CE_Object); + for (int i = 0; i < parameterTypes.length; i++) { + cob.dup() + .constantInstruction(i); + codeWrapArgument(cob, parameterTypes[i], cob.parameterSlot(i)); + cob.aastore(); + } + } else { + cob.aconst_null(); } - } else { - cob.aconst_null(); - } - cob.invokeinterface(IMRE_InvocationHandler_invoke); + cob.invokeinterface(IMRE_InvocationHandler_invoke); - if (returnType == void.class) { - cob.pop() - .return_(); - } else { - codeUnwrapReturnValue(cob, returnType); - } - if (!catchList.isEmpty()) { - var c1 = cob.newBoundLabel(); - for (var exc : catchList) { - cob.exceptionCatch(cob.startLabel(), c1, c1, exc); + if (returnType == void.class) { + cob.pop() + .return_(); + } else { + codeUnwrapReturnValue(cob, returnType); } - cob.athrow(); // just rethrow the exception - var c2 = cob.newBoundLabel(); - cob.exceptionCatchAll(cob.startLabel(), c1, c2) - .new_(CE_UndeclaredThrowableException) - .dup_x1() - .swap() - .invokespecial(MRE_UndeclaredThrowableException_init) - .athrow() - .with(StackMapTableAttribute.of(List.of( - StackMapFrameInfo.of(c1, List.of(), THROWABLE_STACK), - StackMapFrameInfo.of(c2, List.of(), THROWABLE_STACK)))); - } - }); - }); + if (!catchList.isEmpty()) { + var c1 = cob.newBoundLabel(); + for (var exc : catchList) { + cob.exceptionCatch(cob.startLabel(), c1, c1, toClassDesc(exc)); + } + cob.athrow(); // just rethrow the exception + var c2 = cob.newBoundLabel(); + cob.exceptionCatchAll(cob.startLabel(), c1, c2) + .new_(CE_UndeclaredThrowableException) + .dup_x1() + .swap() + .invokespecial(MRE_UndeclaredThrowableException_init) + .athrow() + .with(StackMapTableAttribute.of(List.of( + StackMapFrameInfo.of(c1, List.of(), THROWABLE_STACK), + StackMapFrameInfo.of(c2, List.of(), THROWABLE_STACK)))); + } + })); } /** From 7d0da2c0190c27f8e2cf89557e31f5d16ab4950e Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Thu, 21 Dec 2023 13:45:57 +0100 Subject: [PATCH 07/23] applied the recommended changes --- .../classes/jdk/internal/classfile/impl/StackCounter.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java b/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java index db524932945be..f7c690e86edf5 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java @@ -35,7 +35,7 @@ import static java.lang.classfile.ClassFile.*; import java.lang.classfile.constantpool.Utf8Entry; import java.lang.constant.MethodTypeDesc; -import java.util.LinkedList; +import java.util.ArrayDeque; public final class StackCounter { @@ -59,7 +59,7 @@ static StackCounter of(DirectCodeBuilder dcb, BufWriterImpl buf) { private final Utf8Entry methodName; private final Utf8Entry methodDesc; private final SplitConstantPool cp; - private final LinkedList targets; + private final ArrayDeque targets; private final BitSet visited; private void jump(int targetBci) { @@ -102,7 +102,7 @@ public StackCounter(LabelContext labelContext, this.methodName = methodName; this.methodDesc = methodDesc; this.cp = cp; - targets = new LinkedList<>(); + targets = new ArrayDeque<>(); maxStack = stack = rets = 0; for (var h : handlers) targets.add(new Target(labelContext.labelToBci(h.handler), 1)); maxLocals = isStatic ? 0 : 1; From 41e879348c8f2ea70b25119e65527b81281c33ac Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Thu, 21 Dec 2023 14:28:39 +0100 Subject: [PATCH 08/23] minor StackCounter fix --- .../classes/jdk/internal/classfile/impl/StackCounter.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java b/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java index f7c690e86edf5..a8f8c31af210c 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java @@ -78,10 +78,8 @@ private void ensureLocalSlot(int index) { } private boolean next() { - var it = targets.iterator(); - while (it.hasNext()) { - var en = it.next(); - it.remove(); + Target en; + while ((en = targets.poll()) != null) { if (!visited.get(en.bci)) { bcs.nextBci = en.bci; stack = en.stack; From c8f1d304358e19872450cd29449d82675f9bbe3e Mon Sep 17 00:00:00 2001 From: Adam Sotona <10807609+asotona@users.noreply.github.com> Date: Wed, 3 Jan 2024 12:50:19 +0100 Subject: [PATCH 09/23] Update src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java Co-authored-by: ExE Boss <3889017+ExE-Boss@users.noreply.github.com> --- .../share/classes/jdk/internal/classfile/impl/StackCounter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java b/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java index a8f8c31af210c..1ba90f0323a8b 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java @@ -59,7 +59,7 @@ static StackCounter of(DirectCodeBuilder dcb, BufWriterImpl buf) { private final Utf8Entry methodName; private final Utf8Entry methodDesc; private final SplitConstantPool cp; - private final ArrayDeque targets; + private final Queue targets; private final BitSet visited; private void jump(int targetBci) { From c6b761a157e66ccba30df68efa2849a92371acf2 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Wed, 3 Jan 2024 13:32:41 +0100 Subject: [PATCH 10/23] StackCounter fix --- .../share/classes/jdk/internal/classfile/impl/StackCounter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java b/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java index 1ba90f0323a8b..fe6042eaf79a0 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java @@ -36,6 +36,7 @@ import java.lang.classfile.constantpool.Utf8Entry; import java.lang.constant.MethodTypeDesc; import java.util.ArrayDeque; +import java.util.Queue; public final class StackCounter { From ef71aff15f37b85b744965d01a75dc036c41b855 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Mon, 8 Jan 2024 14:33:23 +0100 Subject: [PATCH 11/23] Revert "performance improvements" This reverts commit 75e31e3a873d45484de253fe09075b0eacc26b17. --- .../java/lang/reflect/ProxyGenerator.java | 111 +++++++++--------- 1 file changed, 53 insertions(+), 58 deletions(-) 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 f6d822108565b..bc8d08228248b 100644 --- a/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java +++ b/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java @@ -294,17 +294,6 @@ public Void run() { return classFile; } - /** - * {@return the entries of the given type} - * @param type the {@code Class} objects, no primitives nor arrays - */ - 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('.', '/'))); - return ces; - } - /** * {@return the {@code ClassDesc} of the given type} * @param type the {@code Class} object @@ -428,7 +417,7 @@ private static void checkReturnTypes(List methods) { * given list of declared exceptions, indicating that no exceptions * need to be caught. */ - private static List> computeUniqueCatchList(Class[] exceptions) { + private static List computeUniqueCatchList(Class[] exceptions) { List> uniqueList = new ArrayList<>(); // unique exceptions to catch @@ -476,7 +465,7 @@ private static List> computeUniqueCatchList(Class[] exceptions) { // This exception is unique (so far): add it to the list to catch. uniqueList.add(ex); } - return uniqueList; + return uniqueList.stream().map(ProxyGenerator::toClassDesc).toList(); } /** @@ -541,7 +530,7 @@ private byte[] generateClassFile() { return classfileContext.build(classEntry, cp, clb -> { TEMPLATE.forEach(clb); clb.withFlags(accessFlags); - clb.withInterfaces(toClassEntries(cp, interfaces)); + clb.withInterfaceSymbols(interfaces.stream().map(ProxyGenerator::toClassDesc).toList()); for (List sigmethods : proxyMethods.values()) { for (ProxyMethod pm : sigmethods) { @@ -741,54 +730,60 @@ private void generateMethod(ClassBuilder clb, ClassEntry className) { int accessFlags = (method.isVarArgs()) ? ACC_VARARGS | ACC_PUBLIC | ACC_FINAL : ACC_PUBLIC | ACC_FINAL; var catchList = computeUniqueCatchList(exceptionTypes); - clb.withMethod(cp.utf8Entry(method.getName()), cp.utf8Entry(desc.toString()), 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))); - - if (parameterTypes.length > 0) { - // Create an array and fill with the parameters converting primitives to wrappers - cob.constantInstruction(parameterTypes.length) - .anewarray(CE_Object); - for (int i = 0; i < parameterTypes.length; i++) { - cob.dup() - .constantInstruction(i); - codeWrapArgument(cob, parameterTypes[i], cob.parameterSlot(i)); - cob.aastore(); - } - } else { - cob.aconst_null(); + clb.withMethod(cp.utf8Entry(method.getName()), cp.utf8Entry(desc.toString()), accessFlags, mb -> { + List exceptionClassEntries = Arrays.asList(exceptionTypes) + .stream() + .map(ProxyGenerator::toClassDesc) + .map(cp::classEntry) + .toList(); + mb.with(ExceptionsAttribute.of(exceptionClassEntries)); + mb.withCode(cob -> { + cob.aload(cob.receiverSlot()) + .getfield(FRE_Proxy_h) + .aload(cob.receiverSlot()) + .getstatic(cp.fieldRefEntry(className, cp.nameAndTypeEntry(methodFieldName, UE_Method))); + + if (parameterTypes.length > 0) { + // Create an array and fill with the parameters converting primitives to wrappers + cob.constantInstruction(parameterTypes.length) + .anewarray(CE_Object); + for (int i = 0; i < parameterTypes.length; i++) { + cob.dup() + .constantInstruction(i); + codeWrapArgument(cob, parameterTypes[i], cob.parameterSlot(i)); + cob.aastore(); } + } else { + cob.aconst_null(); + } - cob.invokeinterface(IMRE_InvocationHandler_invoke); + cob.invokeinterface(IMRE_InvocationHandler_invoke); - if (returnType == void.class) { - cob.pop() - .return_(); - } else { - codeUnwrapReturnValue(cob, returnType); - } - if (!catchList.isEmpty()) { - var c1 = cob.newBoundLabel(); - for (var exc : catchList) { - cob.exceptionCatch(cob.startLabel(), c1, c1, toClassDesc(exc)); - } - cob.athrow(); // just rethrow the exception - var c2 = cob.newBoundLabel(); - cob.exceptionCatchAll(cob.startLabel(), c1, c2) - .new_(CE_UndeclaredThrowableException) - .dup_x1() - .swap() - .invokespecial(MRE_UndeclaredThrowableException_init) - .athrow() - .with(StackMapTableAttribute.of(List.of( - StackMapFrameInfo.of(c1, List.of(), THROWABLE_STACK), - StackMapFrameInfo.of(c2, List.of(), THROWABLE_STACK)))); + if (returnType == void.class) { + cob.pop() + .return_(); + } else { + codeUnwrapReturnValue(cob, returnType); + } + if (!catchList.isEmpty()) { + var c1 = cob.newBoundLabel(); + for (var exc : catchList) { + cob.exceptionCatch(cob.startLabel(), c1, c1, exc); } - })); + cob.athrow(); // just rethrow the exception + var c2 = cob.newBoundLabel(); + cob.exceptionCatchAll(cob.startLabel(), c1, c2) + .new_(CE_UndeclaredThrowableException) + .dup_x1() + .swap() + .invokespecial(MRE_UndeclaredThrowableException_init) + .athrow() + .with(StackMapTableAttribute.of(List.of( + StackMapFrameInfo.of(c1, List.of(), THROWABLE_STACK), + StackMapFrameInfo.of(c2, List.of(), THROWABLE_STACK)))); + } + }); + }); } /** From 11ba1ba812b65c0fd607e4f8c222698a99804867 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Mon, 8 Jan 2024 14:33:39 +0100 Subject: [PATCH 12/23] Revert "performance improvements" This reverts commit a21fb26413112aebb7a98137ec11a938e5dd8846. --- .../java/lang/reflect/ProxyGenerator.java | 161 +++++++++--------- 1 file changed, 80 insertions(+), 81 deletions(-) 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 bc8d08228248b..2685907e561a0 100644 --- a/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java +++ b/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java @@ -125,8 +125,6 @@ final class ProxyGenerator { private static final MethodRefEntry MRE_UndeclaredThrowableException_init; private static final ClassEntry CE_UndeclaredThrowableException; private static final ClassEntry CE_Throwable; - private static final ClassEntry CE_NoSuchMethodException; - private static final ClassEntry CE_ClassNotFoundException; private static final List THROWABLE_STACK; static { @@ -164,8 +162,6 @@ T next() { cp.interfaceMethodRefEntry(CD_InvocationHandler, "invoke", MTD_Object_Object_Method_ObjectArray), cp.methodRefEntry(CD_UndeclaredThrowableException, INIT_NAME, MTD_void_Throwable), cp.classEntry(CD_UndeclaredThrowableException), - cp.classEntry(CD_NoSuchMethodException), - cp.classEntry(CD_ClassNotFoundException), cp.classEntry(CD_Throwable) }; })); @@ -192,8 +188,6 @@ T next() { IMRE_InvocationHandler_invoke = q.next(); MRE_UndeclaredThrowableException_init = q.next(); CE_UndeclaredThrowableException = q.next(); - CE_NoSuchMethodException = q.next(); - CE_ClassNotFoundException = q.next(); CE_Throwable = q.next(); THROWABLE_STACK = List.of(StackMapFrameInfo.ObjectVerificationTypeInfo.of(CE_Throwable)); } @@ -615,39 +609,42 @@ private static void generateConstructor(ClassBuilder clb) { * Generate the static initializer method for the proxy class. */ 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) - .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) - .athrow() - .with(StackMapTableAttribute.of(List.of( - StackMapFrameInfo.of(c1, List.of(), THROWABLE_STACK), - StackMapFrameInfo.of(c2, List.of(), THROWABLE_STACK)))); - }); + clb.withMethodBody(CLASS_INIT_NAME, MTD_void, ACC_STATIC, cob -> cob + // Put ClassLoader at local variable index 0, used by + // Class.forName(String, boolean, ClassLoader) calls + .ldc(classEntry) + .invokevirtual(MRE_Class_getClassLoader) + .astore(0) + .trying(tryb -> { + for (List sigmethods : proxyMethods.values()) { + for (ProxyMethod pm : sigmethods) { + pm.codeFieldInitialization(tryb, classEntry); + } + } + tryb.return_(); + }, cb -> { + var c1 = cob.newLabel(); + cb.catching(CD_NoSuchMethodException, nsmb -> nsmb + .labelBinding(c1) + .new_(CE_NoSuchMethodError) + .dup_x1() + .swap() + .invokevirtual(MRE_Throwable_getMessage) + .invokespecial(MRE_NoSuchMethodError_init) + .athrow()); + var c2 = cob.newLabel(); + cb.catching(CD_ClassNotFoundException, cnfb -> cnfb + .labelBinding(c2) + .new_(CE_NoClassDefFoundError) + .dup_x1() + .swap() + .invokevirtual(MRE_Throwable_getMessage) + .invokespecial(MRE_NoClassDefFoundError_init) + .athrow()); + cob.with(StackMapTableAttribute.of(List.of( + StackMapFrameInfo.of(c1, List.of(), THROWABLE_STACK), + StackMapFrameInfo.of(c2, List.of(), THROWABLE_STACK)))); + })); } /** @@ -737,52 +734,54 @@ private void generateMethod(ClassBuilder clb, ClassEntry className) { .map(cp::classEntry) .toList(); mb.with(ExceptionsAttribute.of(exceptionClassEntries)); - mb.withCode(cob -> { - cob.aload(cob.receiverSlot()) - .getfield(FRE_Proxy_h) - .aload(cob.receiverSlot()) - .getstatic(cp.fieldRefEntry(className, cp.nameAndTypeEntry(methodFieldName, UE_Method))); - - if (parameterTypes.length > 0) { - // Create an array and fill with the parameters converting primitives to wrappers - cob.constantInstruction(parameterTypes.length) - .anewarray(CE_Object); - for (int i = 0; i < parameterTypes.length; i++) { - cob.dup() - .constantInstruction(i); - codeWrapArgument(cob, parameterTypes[i], cob.parameterSlot(i)); - cob.aastore(); + mb.withCode(cob -> + cob.trying(tryb -> { + tryb.aload(tryb.receiverSlot()) + .getfield(FRE_Proxy_h) + .aload(tryb.receiverSlot()) + .getstatic(cp.fieldRefEntry(className, cp.nameAndTypeEntry(methodFieldName, UE_Method))); + + if (parameterTypes.length > 0) { + // Create an array and fill with the parameters converting primitives to wrappers + tryb.constantInstruction(parameterTypes.length) + .anewarray(CE_Object); + for (int i = 0; i < parameterTypes.length; i++) { + tryb.dup() + .constantInstruction(i); + codeWrapArgument(tryb, parameterTypes[i], tryb.parameterSlot(i)); + tryb.aastore(); + } + } else { + tryb.aconst_null(); } - } else { - cob.aconst_null(); - } - cob.invokeinterface(IMRE_InvocationHandler_invoke); + tryb.invokeinterface(IMRE_InvocationHandler_invoke); - if (returnType == void.class) { - cob.pop() - .return_(); - } else { - codeUnwrapReturnValue(cob, returnType); - } - if (!catchList.isEmpty()) { - var c1 = cob.newBoundLabel(); - for (var exc : catchList) { - cob.exceptionCatch(cob.startLabel(), c1, c1, exc); + if (returnType == void.class) { + tryb.pop() + .return_(); + } else { + codeUnwrapReturnValue(tryb, returnType); } - cob.athrow(); // just rethrow the exception - var c2 = cob.newBoundLabel(); - cob.exceptionCatchAll(cob.startLabel(), c1, c2) - .new_(CE_UndeclaredThrowableException) - .dup_x1() - .swap() - .invokespecial(MRE_UndeclaredThrowableException_init) - .athrow() - .with(StackMapTableAttribute.of(List.of( - StackMapFrameInfo.of(c1, List.of(), THROWABLE_STACK), - StackMapFrameInfo.of(c2, List.of(), THROWABLE_STACK)))); - } - }); + }, catchBuilder -> { + if (!catchList.isEmpty()) { + var c1 = cob.newLabel(); + catchBuilder.catchingMulti(catchList, ehb -> ehb + .labelBinding(c1) + .athrow()); // just rethrow the exception + var c2 = cob.newLabel(); + catchBuilder.catching(CD_Throwable, ehb -> ehb + .labelBinding(c2) + .new_(CE_UndeclaredThrowableException) + .dup_x1() + .swap() + .invokespecial(MRE_UndeclaredThrowableException_init) + .athrow()); + cob.with(StackMapTableAttribute.of(List.of( + StackMapFrameInfo.of(c1, List.of(), THROWABLE_STACK), + StackMapFrameInfo.of(c2, List.of(), THROWABLE_STACK)))); + } + })); }); } From f0fae9eb21071cdc78347af0278edc6066b11e08 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Mon, 8 Jan 2024 14:33:48 +0100 Subject: [PATCH 13/23] Revert "performance improvements" This reverts commit 3080d249b1e5c762618784a0b7a88128d7799fc8. --- .../java/lang/reflect/ProxyGenerator.java | 288 ++++++------------ 1 file changed, 97 insertions(+), 191 deletions(-) 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 2685907e561a0..d69b4ce4c6387 100644 --- a/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java +++ b/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java @@ -45,9 +45,6 @@ import java.util.Objects; import static java.lang.classfile.ClassFile.*; -import java.lang.classfile.attribute.StackMapFrameInfo; -import java.lang.classfile.attribute.StackMapTableAttribute; -import java.util.StringJoiner; /** * ProxyGenerator contains the code to generate a dynamic proxy class @@ -57,6 +54,7 @@ * "generateProxyClass" method. */ final class ProxyGenerator { + private static final int CLASSFILE_VERSION = ClassFileFormatVersion.latest().major(); private static final ClassDesc CD_ClassLoader = ClassDesc.ofInternalName("java/lang/ClassLoader"), @@ -107,101 +105,25 @@ final class ProxyGenerator { private static final ProxyMethod equalsMethod; private static final ProxyMethod toStringMethod; - private static final ClassModel TEMPLATE; - - private static final Utf8Entry UE_Method; - private static final MethodRefEntry MRE_Class_getClassLoader; - private static final MethodRefEntry MRE_NoSuchMethodError_init; - private static final ClassEntry CE_NoSuchMethodError; - private static final FieldRefEntry FRE_Proxy_h; - private static final ClassEntry CE_NoClassDefFoundError; - private static final MethodRefEntry MRE_NoClassDefFoundError_init; - private static final MethodRefEntry MRE_Throwable_getMessage; - private static final MethodRefEntry MRE_Class_forName; - private static final ClassEntry CE_Class; - private static final MethodRefEntry MRE_Class_getMethod; - private static final ClassEntry CE_Object; - private static final InterfaceMethodRefEntry IMRE_InvocationHandler_invoke; - private static final MethodRefEntry MRE_UndeclaredThrowableException_init; - private static final ClassEntry CE_UndeclaredThrowableException; - private static final ClassEntry CE_Throwable; - private static final List THROWABLE_STACK; - static { - var cc = ClassFile.of(); - var entries = new ArrayList(20); - var q = new Object() { - PoolEntry[] entries; - int i; - @SuppressWarnings("unchecked") - T next() { - return (T) TEMPLATE.constantPool().entryByIndex(entries[i++].index()); - } - }; - TEMPLATE = cc.parse(cc.build(CD_Proxy, clb -> { - clb.withSuperclass(CD_Proxy); - generateConstructor(clb); - generateLookupAccessor(clb); - var cp = clb.constantPool(); - q.entries = new PoolEntry[] { - cp.utf8Entry("m0"), - cp.utf8Entry("m1"), - cp.utf8Entry("m2"), - cp.utf8Entry(CD_Method), - cp.methodRefEntry(CD_Class, "getClassLoader", MTD_ClassLoader), - cp.classEntry(CD_NoSuchMethodError), - cp.methodRefEntry(CD_NoSuchMethodError, INIT_NAME, MTD_void_String), - cp.fieldRefEntry(CD_Proxy, handlerFieldName, CD_InvocationHandler), - cp.classEntry(CD_NoClassDefFoundError), - cp.methodRefEntry(CD_NoClassDefFoundError, INIT_NAME, MTD_void_String), - cp.methodRefEntry(CD_Throwable, "getMessage", MTD_String), - cp.methodRefEntry(CD_Class, "forName", MTD_Class_String_boolean_ClassLoader), - cp.classEntry(CD_Class), - cp.methodRefEntry(CD_Class, "getMethod", MTD_Method_String_ClassArray), - cp.classEntry(CD_Object), - cp.interfaceMethodRefEntry(CD_InvocationHandler, "invoke", MTD_Object_Object_Method_ObjectArray), - cp.methodRefEntry(CD_UndeclaredThrowableException, INIT_NAME, MTD_void_Throwable), - cp.classEntry(CD_UndeclaredThrowableException), - cp.classEntry(CD_Throwable) - }; - })); - try { - hashCodeMethod = new ProxyMethod(Object.class.getMethod("hashCode"), q.next()); - equalsMethod = new ProxyMethod(Object.class.getMethod("equals", Object.class), q.next()); - toStringMethod = new ProxyMethod(Object.class.getMethod("toString"), q.next()); + hashCodeMethod = new ProxyMethod(Object.class.getMethod("hashCode"), "m0"); + equalsMethod = new ProxyMethod(Object.class.getMethod("equals", Object.class), "m1"); + toStringMethod = new ProxyMethod(Object.class.getMethod("toString"), "m2"); } catch (NoSuchMethodException e) { throw new NoSuchMethodError(e.getMessage()); } - UE_Method = q.next(); - MRE_Class_getClassLoader = q.next(); - CE_NoSuchMethodError = q.next(); - MRE_NoSuchMethodError_init = q.next(); - FRE_Proxy_h = q.next(); - CE_NoClassDefFoundError = q.next(); - MRE_NoClassDefFoundError_init = q.next(); - MRE_Throwable_getMessage = q.next(); - MRE_Class_forName = q.next(); - CE_Class = q.next(); - MRE_Class_getMethod = q.next(); - CE_Object = q.next(); - IMRE_InvocationHandler_invoke = q.next(); - MRE_UndeclaredThrowableException_init = q.next(); - CE_UndeclaredThrowableException = q.next(); - CE_Throwable = q.next(); - THROWABLE_STACK = List.of(StackMapFrameInfo.ObjectVerificationTypeInfo.of(CE_Throwable)); } /** * Classfile context */ private final ClassFile classfileContext; - private final ConstantPoolBuilder cp; /** * Name of proxy class */ - private ClassEntry classEntry; + private final ClassDesc classDesc; /** * Proxy interfaces @@ -236,11 +158,9 @@ T next() { 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.classDesc = ClassDesc.of(className); this.interfaces = interfaces; this.accessFlags = accessFlags; } @@ -491,52 +411,56 @@ private static void collectCompatibleTypes(Class[] from, * class file generation process. */ private byte[] generateClassFile() { - /* - * Add proxy methods for the hashCode, equals, - * and toString methods of java.lang.Object. This is done before - * the methods from the proxy interfaces so that the methods from - * java.lang.Object take precedence over duplicate methods in the - * proxy interfaces. - */ - addProxyMethod(hashCodeMethod); - addProxyMethod(equalsMethod); - addProxyMethod(toStringMethod); + return classfileContext.build(classDesc, clb -> { + clb.withFlags(accessFlags); + clb.withSuperclass(CD_Proxy); + clb.withInterfaceSymbols(interfaces.stream().map(ProxyGenerator::toClassDesc).toList()); + clb.withVersion(CLASSFILE_VERSION, 0); - /* - * Accumulate all of the methods from the proxy interfaces. - */ - for (Class intf : interfaces) { - for (Method m : intf.getMethods()) { - if (!Modifier.isStatic(m.getModifiers())) { - addProxyMethod(m, intf, cp); + /* + * Add proxy methods for the hashCode, equals, + * and toString methods of java.lang.Object. This is done before + * the methods from the proxy interfaces so that the methods from + * java.lang.Object take precedence over duplicate methods in the + * proxy interfaces. + */ + addProxyMethod(hashCodeMethod); + addProxyMethod(equalsMethod); + addProxyMethod(toStringMethod); + + /* + * Accumulate all of the methods from the proxy interfaces. + */ + for (Class intf : interfaces) { + for (Method m : intf.getMethods()) { + if (!Modifier.isStatic(m.getModifiers())) { + addProxyMethod(m, intf); + } } } - } - /* - * For each set of proxy methods with the same signature, - * verify that the methods' return types are compatible. - */ - for (List sigmethods : proxyMethods.values()) { - checkReturnTypes(sigmethods); - } + /* + * For each set of proxy methods with the same signature, + * verify that the methods' return types are compatible. + */ + for (List sigmethods : proxyMethods.values()) { + checkReturnTypes(sigmethods); + } - return classfileContext.build(classEntry, cp, clb -> { - TEMPLATE.forEach(clb); - clb.withFlags(accessFlags); - clb.withInterfaceSymbols(interfaces.stream().map(ProxyGenerator::toClassDesc).toList()); + 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); + clb.withField(pm.methodFieldName, CD_Method, ACC_PRIVATE | ACC_STATIC | ACC_FINAL); // Generate code for proxy method - pm.generateMethod(clb, classEntry); + pm.generateMethod(clb, classDesc); } } generateStaticInitializer(clb); + generateLookupAccessor(clb); }); } @@ -553,7 +477,7 @@ private byte[] generateClassFile() { * 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(); @@ -579,7 +503,7 @@ private void addProxyMethod(Method m, Class fromClass, ConstantPoolBuilder cp } sigmethods.add(new ProxyMethod(m, sig, m.getSharedParameterTypes(), returnType, exceptionTypes, fromClass, - cp.utf8Entry("m" + proxyMethodCount++))); + "m" + proxyMethodCount++)); } /** @@ -597,7 +521,7 @@ private void addProxyMethod(ProxyMethod pm) { /** * 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)) @@ -612,39 +536,31 @@ private void generateStaticInitializer(ClassBuilder clb) { clb.withMethodBody(CLASS_INIT_NAME, MTD_void, ACC_STATIC, cob -> cob // Put ClassLoader at local variable index 0, used by // Class.forName(String, boolean, ClassLoader) calls - .ldc(classEntry) - .invokevirtual(MRE_Class_getClassLoader) + .constantInstruction(classDesc) + .invokevirtual(CD_Class, "getClassLoader", MTD_ClassLoader) .astore(0) .trying(tryb -> { for (List sigmethods : proxyMethods.values()) { for (ProxyMethod pm : sigmethods) { - pm.codeFieldInitialization(tryb, classEntry); + pm.codeFieldInitialization(tryb, classDesc); } } tryb.return_(); - }, cb -> { - var c1 = cob.newLabel(); - cb.catching(CD_NoSuchMethodException, nsmb -> nsmb - .labelBinding(c1) - .new_(CE_NoSuchMethodError) + }, cb -> cb + .catching(CD_NoSuchMethodException, nsmb -> nsmb + .new_(CD_NoSuchMethodError) .dup_x1() .swap() - .invokevirtual(MRE_Throwable_getMessage) - .invokespecial(MRE_NoSuchMethodError_init) - .athrow()); - var c2 = cob.newLabel(); - cb.catching(CD_ClassNotFoundException, cnfb -> cnfb - .labelBinding(c2) - .new_(CE_NoClassDefFoundError) + .invokevirtual(CD_Throwable, "getMessage", MTD_String) + .invokespecial(CD_NoSuchMethodError, INIT_NAME, MTD_void_String) + .athrow()) + .catching(CD_ClassNotFoundException, cnfb -> cnfb + .new_(CD_NoClassDefFoundError) .dup_x1() .swap() - .invokevirtual(MRE_Throwable_getMessage) - .invokespecial(MRE_NoClassDefFoundError_init) - .athrow()); - cob.with(StackMapTableAttribute.of(List.of( - StackMapFrameInfo.of(c1, List.of(), THROWABLE_STACK), - StackMapFrameInfo.of(c2, List.of(), THROWABLE_STACK)))); - })); + .invokevirtual(CD_Throwable, "getMessage", MTD_String) + .invokespecial(CD_NoClassDefFoundError, INIT_NAME, MTD_void_String) + .athrow()))); } /** @@ -652,7 +568,7 @@ private void generateStaticInitializer(ClassBuilder clb) { * 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, @@ -688,12 +604,12 @@ private static class ProxyMethod { private final Class fromClass; private final Class[] parameterTypes; private final Class returnType; - private final Utf8Entry methodFieldName; + private final String methodFieldName; private Class[] exceptionTypes; private ProxyMethod(Method method, String sig, Class[] parameterTypes, Class returnType, Class[] exceptionTypes, - Class fromClass, Utf8Entry methodFieldName) { + Class fromClass, String methodFieldName) { this.method = method; this.shortSignature = sig; this.parameterTypes = parameterTypes; @@ -709,7 +625,7 @@ private ProxyMethod(Method method, String sig, Class[] parameterTypes, * @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, String methodFieldName) { this(method, method.toShortSignature(), method.getSharedParameterTypes(), method.getReturnType(), method.getSharedExceptionTypes(), method.getDeclaringClass(), methodFieldName); @@ -718,33 +634,31 @@ private ProxyMethod(Method method, Utf8Entry methodFieldName) { /** * Generate this method, including the code and exception table entry. */ - private void generateMethod(ClassBuilder clb, ClassEntry className) { - var cp = clb.constantPool(); - var desc = new StringJoiner("", "(", ")" + returnType.descriptorString()); - for (var pt : parameterTypes) { - desc.add(pt.descriptorString()); - } + private void generateMethod(ClassBuilder clb, ClassDesc className) { + MethodTypeDesc desc = MethodTypeDesc.of(toClassDesc(returnType), + Arrays.stream(parameterTypes).map(ProxyGenerator::toClassDesc).toArray(ClassDesc[]::new)); int accessFlags = (method.isVarArgs()) ? ACC_VARARGS | ACC_PUBLIC | ACC_FINAL : ACC_PUBLIC | ACC_FINAL; var catchList = computeUniqueCatchList(exceptionTypes); - clb.withMethod(cp.utf8Entry(method.getName()), cp.utf8Entry(desc.toString()), accessFlags, mb -> { + clb.withMethod(method.getName(), desc, accessFlags, mb -> { + ConstantPoolBuilder cpb = mb.constantPool(); List exceptionClassEntries = Arrays.asList(exceptionTypes) .stream() .map(ProxyGenerator::toClassDesc) - .map(cp::classEntry) + .map(cpb::classEntry) .toList(); mb.with(ExceptionsAttribute.of(exceptionClassEntries)); mb.withCode(cob -> cob.trying(tryb -> { tryb.aload(tryb.receiverSlot()) - .getfield(FRE_Proxy_h) + .getfield(CD_Proxy, handlerFieldName, CD_InvocationHandler) .aload(tryb.receiverSlot()) - .getstatic(cp.fieldRefEntry(className, cp.nameAndTypeEntry(methodFieldName, UE_Method))); + .getstatic(className, methodFieldName, CD_Method); if (parameterTypes.length > 0) { // Create an array and fill with the parameters converting primitives to wrappers tryb.constantInstruction(parameterTypes.length) - .anewarray(CE_Object); + .anewarray(CD_Object); for (int i = 0; i < parameterTypes.length; i++) { tryb.dup() .constantInstruction(i); @@ -755,7 +669,7 @@ private void generateMethod(ClassBuilder clb, ClassEntry className) { tryb.aconst_null(); } - tryb.invokeinterface(IMRE_InvocationHandler_invoke); + tryb.invokeinterface(CD_InvocationHandler, "invoke", MTD_Object_Object_Method_ObjectArray); if (returnType == void.class) { tryb.pop() @@ -765,21 +679,15 @@ private void generateMethod(ClassBuilder clb, ClassEntry className) { } }, catchBuilder -> { if (!catchList.isEmpty()) { - var c1 = cob.newLabel(); catchBuilder.catchingMulti(catchList, ehb -> ehb - .labelBinding(c1) .athrow()); // just rethrow the exception - var c2 = cob.newLabel(); + catchBuilder.catching(CD_Throwable, ehb -> ehb - .labelBinding(c2) - .new_(CE_UndeclaredThrowableException) + .new_(CD_UndeclaredThrowableException) .dup_x1() .swap() - .invokespecial(MRE_UndeclaredThrowableException_init) + .invokespecial(CD_UndeclaredThrowableException, INIT_NAME, MTD_void_Throwable) .athrow()); - cob.with(StackMapTableAttribute.of(List.of( - StackMapFrameInfo.of(c1, List.of(), THROWABLE_STACK), - StackMapFrameInfo.of(c2, List.of(), THROWABLE_STACK)))); } })); }); @@ -795,7 +703,7 @@ private void codeWrapArgument(CodeBuilder cob, Class type, int slot) { if (type.isPrimitive()) { cob.loadInstruction(TypeKind.from(type).asLoadable(), slot); PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(type); - cob.invokestatic(prim.wrapperMethodRef); + cob.invokestatic(prim.wrapperClass, "valueOf", prim.wrapperValueOf); } else { cob.aload(slot); } @@ -810,8 +718,8 @@ private void codeUnwrapReturnValue(CodeBuilder cob, Class type) { if (type.isPrimitive()) { PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(type); - cob.checkcast(prim.wrapperClass) - .invokevirtual(prim.unwrapMethodRef) + cob.typeCheckInstruction(Opcode.CHECKCAST, prim.wrapperClass) + .invokevirtual(prim.wrapperClass, prim.unwrapMethodName, prim.unwrapMethodType) .returnInstruction(TypeKind.from(type).asLoadable()); } else { cob.checkcast(toClassDesc(type)) @@ -824,13 +732,12 @@ private void codeUnwrapReturnValue(CodeBuilder cob, Class type) { * 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(); + private void codeFieldInitialization(CodeBuilder cob, ClassDesc className) { codeClassForName(cob, fromClass); cob.ldc(method.getName()) .constantInstruction(parameterTypes.length) - .anewarray(CE_Class); + .anewarray(CD_Class); // Construct an array with the parameter types mapping primitives to Wrapper types for (int i = 0; i < parameterTypes.length; i++) { @@ -838,15 +745,15 @@ private void codeFieldInitialization(CodeBuilder cob, ClassEntry className) { .constantInstruction(i); if (parameterTypes[i].isPrimitive()) { PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(parameterTypes[i]); - cob.getstatic(prim.typeFieldRef); + cob.getstatic(prim.wrapperClass, "TYPE", CD_Class); } else { codeClassForName(cob, parameterTypes[i]); } cob.aastore(); } // lookup the method - cob.invokevirtual(MRE_Class_getMethod) - .putstatic(cp.fieldRefEntry(className, cp.nameAndTypeEntry(methodFieldName, UE_Method))); + cob.invokevirtual(CD_Class, "getMethod", MTD_Method_String_ClassArray) + .putstatic(className, methodFieldName, CD_Method); } /* @@ -861,10 +768,10 @@ private void codeFieldInitialization(CodeBuilder cob, ClassEntry className) { * loader is anticipated at local variable index 0. */ private void codeClassForName(CodeBuilder cob, Class cl) { - cob.ldc(cl.getName()) + cob.constantInstruction(Opcode.LDC, cl.getName()) .iconst_0() // false .aload(0)// classLoader - .invokestatic(MRE_Class_forName); + .invokestatic(CD_Class, "forName", MTD_Class_String_boolean_ClassLoader); } @Override @@ -873,7 +780,6 @@ public String toString() { } } - 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 @@ -890,28 +796,28 @@ private enum PrimitiveTypeInfo { BOOLEAN(boolean.class, CD_boolean, CD_Boolean); /** - * CP entry of corresponding wrapper class + * internal name of corresponding wrapper class */ - private final ClassEntry wrapperClass; + private final ClassDesc wrapperClass; /** - * CP entry for wrapper class "valueOf" factory method + * method descriptor for wrapper class "valueOf" factory method */ - private final MethodRefEntry wrapperMethodRef; + private final MethodTypeDesc wrapperValueOf; /** - * CP entry of wrapper class method for retrieving primitive value + * name of wrapper class method for retrieving primitive value */ - private final MethodRefEntry unwrapMethodRef; + private final String unwrapMethodName; /** - * CP entry of wrapper class TYPE field + * descriptor of same method */ - 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.wrapperValueOf = MethodTypeDesc.of(wrapperClass, baseType); + this.unwrapMethodName = primitiveClass.getName() + "Value"; + this.unwrapMethodType = MethodTypeDesc.of(baseType); } public static PrimitiveTypeInfo get(Class cl) { From bb2cf21cd99299f4e1a6a9cda12dd07ad70f16f6 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Mon, 8 Jan 2024 14:33:55 +0100 Subject: [PATCH 14/23] Revert "8294961: Convert java.base/java.lang.reflect.ProxyGenerator to use the Classfile API to generate proxy classes" This reverts commit 0267d6578ebbf6f12159a0f5fa7874a52a020ef4. --- .../classes/java/lang/reflect/Proxy.java | 1 - .../java/lang/reflect/ProxyGenerator.java | 677 +++++++++++------- 2 files changed, 425 insertions(+), 253 deletions(-) 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 24c6aaee01ce0..3c37153b875ae 100644 --- a/src/java.base/share/classes/java/lang/reflect/Proxy.java +++ b/src/java.base/share/classes/java/lang/reflect/Proxy.java @@ -618,7 +618,6 @@ private static boolean isDebug(String flag) { private final List> interfaces; private final ProxyClassContext context; ProxyBuilder(ClassLoader loader, List> interfaces) { - Objects.requireNonNull(interfaces); if (!VM.isModuleSystemInited()) { throw new InternalError("Proxy is not supported until " + "module system is fully initialized"); 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 d69b4ce4c6387..8911ba1190320 100644 --- a/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java +++ b/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java @@ -25,15 +25,16 @@ package java.lang.reflect; -import java.lang.classfile.*; -import java.lang.classfile.constantpool.*; -import java.lang.classfile.attribute.ExceptionsAttribute; +import jdk.internal.org.objectweb.asm.ClassWriter; +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.Wrapper; import sun.security.action.GetBooleanAction; import java.io.IOException; -import java.lang.constant.ClassDesc; -import static java.lang.constant.ConstantDescs.*; -import java.lang.constant.MethodTypeDesc; +import java.lang.invoke.MethodType; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; @@ -42,9 +43,8 @@ import java.util.List; import java.util.ListIterator; import java.util.Map; -import java.util.Objects; -import static java.lang.classfile.ClassFile.*; +import static jdk.internal.org.objectweb.asm.Opcodes.*; /** * ProxyGenerator contains the code to generate a dynamic proxy class @@ -53,35 +53,33 @@ * The external interface to ProxyGenerator is the static * "generateProxyClass" method. */ -final class ProxyGenerator { +final class ProxyGenerator extends ClassWriter { private static final int CLASSFILE_VERSION = ClassFileFormatVersion.latest().major(); - - 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"); - - 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); - + private static final String JL_CLASS = "java/lang/Class"; + private static final String JL_OBJECT = "java/lang/Object"; + private static final String JL_THROWABLE = "java/lang/Throwable"; + private static final String JL_CLASS_NOT_FOUND_EX = "java/lang/ClassNotFoundException"; + private static final String JL_ILLEGAL_ACCESS_EX = "java/lang/IllegalAccessException"; + + private static final String JL_NO_CLASS_DEF_FOUND_ERROR = "java/lang/NoClassDefFoundError"; + private static final String JL_NO_SUCH_METHOD_EX = "java/lang/NoSuchMethodException"; + private static final String JL_NO_SUCH_METHOD_ERROR = "java/lang/NoSuchMethodError"; + private static final String JLI_LOOKUP = "java/lang/invoke/MethodHandles$Lookup"; + private static final String JLI_METHODHANDLES = "java/lang/invoke/MethodHandles"; + + private static final String JLR_INVOCATION_HANDLER = "java/lang/reflect/InvocationHandler"; + private static final String JLR_PROXY = "java/lang/reflect/Proxy"; + private static final String JLR_UNDECLARED_THROWABLE_EX = "java/lang/reflect/UndeclaredThrowableException"; + + private static final String LJL_CLASS = "Ljava/lang/Class;"; + private static final String LJL_CLASSLOADER = "Ljava/lang/ClassLoader;"; + private static final String LJLR_METHOD = "Ljava/lang/reflect/Method;"; + private static final String LJLR_INVOCATION_HANDLER = "Ljava/lang/reflect/InvocationHandler;"; + + private static final String MJLR_INVOCATIONHANDLER = "(Ljava/lang/reflect/InvocationHandler;)V"; + + private static final String NAME_CTOR = ""; + private static final String NAME_CLINIT = ""; private static final String NAME_LOOKUP_ACCESSOR = "proxyClassLookup"; private static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; @@ -116,14 +114,14 @@ final class ProxyGenerator { } /** - * Classfile context + * Class loader */ - private final ClassFile classfileContext; + private final ClassLoader loader; /** * Name of proxy class */ - private final ClassDesc classDesc; + private final String className; /** * Proxy interfaces @@ -157,10 +155,9 @@ final class ProxyGenerator { */ private ProxyGenerator(ClassLoader loader, String className, List> interfaces, int accessFlags) { - this.classfileContext = ClassFile.of( - ClassFile.ClassHierarchyResolverOption.of( - ClassHierarchyResolver.ofClassLoading(loader).cached())); - this.classDesc = ClassDesc.of(className); + super(ClassWriter.COMPUTE_FRAMES); + this.loader = loader; + this.className = className; this.interfaces = interfaces; this.accessFlags = accessFlags; } @@ -177,7 +174,6 @@ static byte[] generateProxyClass(ClassLoader loader, final String name, List> interfaces, int accessFlags) { - Objects.requireNonNull(interfaces); ProxyGenerator gen = new ProxyGenerator(loader, name, interfaces, accessFlags); final byte[] classFile = gen.generateClassFile(); @@ -189,7 +185,7 @@ public Void run() { int i = name.lastIndexOf('.'); Path path; if (i > 0) { - Path dir = Path.of(name.substring(0, i).replace('.', '/')); + Path dir = Path.of(dotToSlash(name.substring(0, i))); Files.createDirectories(dir); path = dir.resolve(name.substring(i + 1) + ".class"); } else { @@ -209,11 +205,20 @@ public Void run() { } /** - * {@return the {@code ClassDesc} of the given type} - * @param type the {@code Class} object + * Return an array of the class and interface names from an array of Classes. + * + * @param classes an array of classes or interfaces + * @return the array of class and interface names; or null if classes is + * null or empty */ - private static ClassDesc toClassDesc(Class type) { - return ClassDesc.ofDescriptor(type.descriptorString()); + private static String[] typeNames(List> classes) { + if (classes == null || classes.size() == 0) + return null; + int size = classes.size(); + String[] ifaces = new String[size]; + for (int i = 0; i < size; i++) + ifaces[i] = dotToSlash(classes.get(i).getName()); + return ifaces; } /** @@ -331,7 +336,7 @@ private static void checkReturnTypes(List methods) { * given list of declared exceptions, indicating that no exceptions * need to be caught. */ - private static List computeUniqueCatchList(Class[] exceptions) { + private static List> computeUniqueCatchList(Class[] exceptions) { List> uniqueList = new ArrayList<>(); // unique exceptions to catch @@ -379,7 +384,35 @@ private static List computeUniqueCatchList(Class[] exceptions) { // This exception is unique (so far): add it to the list to catch. uniqueList.add(ex); } - return uniqueList.stream().map(ProxyGenerator::toClassDesc).toList(); + return uniqueList; + } + + /** + * Convert a fully qualified class name that uses '.' as the package + * separator, the external representation used by the Java language + * and APIs, to a fully qualified class name that uses '/' as the + * package separator, the representation used in the class file + * format (see JVMS section {@jvms 4.2}). + */ + private static String dotToSlash(String name) { + return name.replace('.', '/'); + } + + /** + * Return the number of abstract "words", or consecutive local variable + * indexes, required to contain a value of the given type. See JVMS + * section {@jvms 3.6.1}. + *

+ * Note that the original version of the JVMS contained a definition of + * this abstract notion of a "word" in section 3.4, but that definition + * was removed for the second edition. + */ + private static int getWordsPerType(Class type) { + if (type == long.class || type == double.class) { + return 2; + } else { + return 1; + } } /** @@ -406,62 +439,71 @@ private static void collectCompatibleTypes(Class[] from, } } + /** + * Returns the {@link ClassLoader} to be used by the default implementation of {@link + * #getCommonSuperClass(String, String)}, that of this {@link ClassWriter}'s runtime type by + * default. + * + * @return ClassLoader + */ + protected ClassLoader getClassLoader() { + return loader; + } + /** * Generate a class file for the proxy class. This method drives the * class file generation process. */ private byte[] generateClassFile() { - return classfileContext.build(classDesc, clb -> { - clb.withFlags(accessFlags); - clb.withSuperclass(CD_Proxy); - clb.withInterfaceSymbols(interfaces.stream().map(ProxyGenerator::toClassDesc).toList()); - clb.withVersion(CLASSFILE_VERSION, 0); + visit(CLASSFILE_VERSION, accessFlags, dotToSlash(className), null, + JLR_PROXY, typeNames(interfaces)); - /* - * Add proxy methods for the hashCode, equals, - * and toString methods of java.lang.Object. This is done before - * the methods from the proxy interfaces so that the methods from - * java.lang.Object take precedence over duplicate methods in the - * proxy interfaces. - */ - addProxyMethod(hashCodeMethod); - addProxyMethod(equalsMethod); - addProxyMethod(toStringMethod); + /* + * Add proxy methods for the hashCode, equals, + * and toString methods of java.lang.Object. This is done before + * the methods from the proxy interfaces so that the methods from + * java.lang.Object take precedence over duplicate methods in the + * proxy interfaces. + */ + addProxyMethod(hashCodeMethod); + addProxyMethod(equalsMethod); + addProxyMethod(toStringMethod); - /* - * Accumulate all of the methods from the proxy interfaces. - */ - for (Class intf : interfaces) { - for (Method m : intf.getMethods()) { - if (!Modifier.isStatic(m.getModifiers())) { - addProxyMethod(m, intf); - } + /* + * Accumulate all of the methods from the proxy interfaces. + */ + for (Class intf : interfaces) { + for (Method m : intf.getMethods()) { + if (!Modifier.isStatic(m.getModifiers())) { + addProxyMethod(m, intf); } } + } - /* - * For each set of proxy methods with the same signature, - * verify that the methods' return types are compatible. - */ - for (List sigmethods : proxyMethods.values()) { - checkReturnTypes(sigmethods); - } + /* + * For each set of proxy methods with the same signature, + * verify that the methods' return types are compatible. + */ + for (List sigmethods : proxyMethods.values()) { + checkReturnTypes(sigmethods); + } - generateConstructor(clb); + generateConstructor(); - for (List sigmethods : proxyMethods.values()) { - for (ProxyMethod pm : sigmethods) { - // add static field for the Method object - clb.withField(pm.methodFieldName, CD_Method, ACC_PRIVATE | ACC_STATIC | ACC_FINAL); + for (List sigmethods : proxyMethods.values()) { + for (ProxyMethod pm : sigmethods) { + // add static field for the Method object + visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL, pm.methodFieldName, + LJLR_METHOD, null, null); - // Generate code for proxy method - pm.generateMethod(clb, classDesc); - } + // Generate code for proxy method + pm.generateMethod(this, className); } + } - generateStaticInitializer(clb); - generateLookupAccessor(clb); - }); + generateStaticInitializer(); + generateLookupAccessor(); + return toByteArray(); } /** @@ -521,46 +563,82 @@ private void addProxyMethod(ProxyMethod pm) { /** * Generate the constructor method for the proxy class. */ - 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) - .return_()); + private void generateConstructor() { + MethodVisitor ctor = visitMethod(Modifier.PUBLIC, NAME_CTOR, + MJLR_INVOCATIONHANDLER, null, null); + ctor.visitParameter(null, 0); + ctor.visitCode(); + ctor.visitVarInsn(ALOAD, 0); + ctor.visitVarInsn(ALOAD, 1); + ctor.visitMethodInsn(INVOKESPECIAL, JLR_PROXY, NAME_CTOR, + MJLR_INVOCATIONHANDLER, false); + ctor.visitInsn(RETURN); + + // Maxs computed by ClassWriter.COMPUTE_FRAMES, these arguments ignored + ctor.visitMaxs(-1, -1); + ctor.visitEnd(); } /** * Generate the static initializer method for the proxy class. */ - private void generateStaticInitializer(ClassBuilder clb) { - clb.withMethodBody(CLASS_INIT_NAME, MTD_void, ACC_STATIC, cob -> cob - // Put ClassLoader at local variable index 0, used by - // Class.forName(String, boolean, ClassLoader) calls - .constantInstruction(classDesc) - .invokevirtual(CD_Class, "getClassLoader", MTD_ClassLoader) - .astore(0) - .trying(tryb -> { - for (List sigmethods : proxyMethods.values()) { - for (ProxyMethod pm : sigmethods) { - pm.codeFieldInitialization(tryb, classDesc); - } - } - tryb.return_(); - }, cb -> cb - .catching(CD_NoSuchMethodException, nsmb -> nsmb - .new_(CD_NoSuchMethodError) - .dup_x1() - .swap() - .invokevirtual(CD_Throwable, "getMessage", MTD_String) - .invokespecial(CD_NoSuchMethodError, INIT_NAME, MTD_void_String) - .athrow()) - .catching(CD_ClassNotFoundException, cnfb -> cnfb - .new_(CD_NoClassDefFoundError) - .dup_x1() - .swap() - .invokevirtual(CD_Throwable, "getMessage", MTD_String) - .invokespecial(CD_NoClassDefFoundError, INIT_NAME, MTD_void_String) - .athrow()))); + private void generateStaticInitializer() { + + MethodVisitor mv = visitMethod(Modifier.STATIC, NAME_CLINIT, + "()V", null, null); + mv.visitCode(); + Label L_startBlock = new Label(); + Label L_endBlock = new Label(); + Label L_NoMethodHandler = new Label(); + Label L_NoClassHandler = new Label(); + + mv.visitTryCatchBlock(L_startBlock, L_endBlock, L_NoMethodHandler, + JL_NO_SUCH_METHOD_EX); + mv.visitTryCatchBlock(L_startBlock, L_endBlock, L_NoClassHandler, + JL_CLASS_NOT_FOUND_EX); + + // Put ClassLoader at local variable index 0, used by + // Class.forName(String, boolean, ClassLoader) calls + mv.visitLdcInsn(Type.getObjectType(dotToSlash(className))); + mv.visitMethodInsn(INVOKEVIRTUAL, JL_CLASS, + "getClassLoader", "()" + LJL_CLASSLOADER, false); + mv.visitVarInsn(ASTORE, 0); + + mv.visitLabel(L_startBlock); + for (List sigmethods : proxyMethods.values()) { + for (ProxyMethod pm : sigmethods) { + pm.codeFieldInitialization(mv, className); + } + } + mv.visitInsn(RETURN); + mv.visitLabel(L_endBlock); + // Generate exception handler + + mv.visitLabel(L_NoMethodHandler); + mv.visitVarInsn(ASTORE, 1); + mv.visitTypeInsn(Opcodes.NEW, JL_NO_SUCH_METHOD_ERROR); + mv.visitInsn(DUP); + mv.visitVarInsn(ALOAD, 1); + mv.visitMethodInsn(INVOKEVIRTUAL, JL_THROWABLE, + "getMessage", "()Ljava/lang/String;", false); + mv.visitMethodInsn(INVOKESPECIAL, JL_NO_SUCH_METHOD_ERROR, + "", "(Ljava/lang/String;)V", false); + mv.visitInsn(ATHROW); + + mv.visitLabel(L_NoClassHandler); + mv.visitVarInsn(ASTORE, 1); + mv.visitTypeInsn(Opcodes.NEW, JL_NO_CLASS_DEF_FOUND_ERROR); + mv.visitInsn(DUP); + mv.visitVarInsn(ALOAD, 1); + mv.visitMethodInsn(INVOKEVIRTUAL, JL_THROWABLE, + "getMessage", "()Ljava/lang/String;", false); + mv.visitMethodInsn(INVOKESPECIAL, JL_NO_CLASS_DEF_FOUND_ERROR, + "", "(Ljava/lang/String;)V", false); + mv.visitInsn(ATHROW); + + // Maxs computed by ClassWriter.COMPUTE_FRAMES, these arguments ignored + mv.visitMaxs(-1, -1); + mv.visitEnd(); } /** @@ -568,28 +646,39 @@ private void generateStaticInitializer(ClassBuilder clb) { * on this proxy class if the caller's lookup class is java.lang.reflect.Proxy; * otherwise, IllegalAccessException is thrown */ - 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) - .constantInstruction(Opcode.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())); + private void generateLookupAccessor() { + MethodVisitor mv = visitMethod(ACC_PRIVATE | ACC_STATIC, NAME_LOOKUP_ACCESSOR, + "(Ljava/lang/invoke/MethodHandles$Lookup;)Ljava/lang/invoke/MethodHandles$Lookup;", null, + new String[] { JL_ILLEGAL_ACCESS_EX }); + mv.visitCode(); + Label L_illegalAccess = new Label(); + + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKEVIRTUAL, JLI_LOOKUP, "lookupClass", + "()Ljava/lang/Class;", false); + mv.visitLdcInsn(Type.getType(Proxy.class)); + mv.visitJumpInsn(IF_ACMPNE, L_illegalAccess); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKEVIRTUAL, JLI_LOOKUP, "hasFullPrivilegeAccess", + "()Z", false); + mv.visitJumpInsn(IFEQ, L_illegalAccess); + mv.visitMethodInsn(INVOKESTATIC, JLI_METHODHANDLES, "lookup", + "()Ljava/lang/invoke/MethodHandles$Lookup;", false); + mv.visitInsn(ARETURN); + + mv.visitLabel(L_illegalAccess); + mv.visitTypeInsn(Opcodes.NEW, JL_ILLEGAL_ACCESS_EX); + mv.visitInsn(DUP); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKEVIRTUAL, JLI_LOOKUP, "toString", + "()Ljava/lang/String;", false); + mv.visitMethodInsn(INVOKESPECIAL, JL_ILLEGAL_ACCESS_EX, + "", "(Ljava/lang/String;)V", false); + mv.visitInsn(ATHROW); + + // Maxs computed by ClassWriter.COMPUTE_FRAMES, these arguments ignored + mv.visitMaxs(-1, -1); + mv.visitEnd(); } /** @@ -627,70 +716,98 @@ private ProxyMethod(Method method, String sig, Class[] parameterTypes, */ private ProxyMethod(Method method, String methodFieldName) { this(method, method.toShortSignature(), - method.getSharedParameterTypes(), method.getReturnType(), - method.getSharedExceptionTypes(), method.getDeclaringClass(), methodFieldName); + method.getSharedParameterTypes(), method.getReturnType(), + method.getSharedExceptionTypes(), method.getDeclaringClass(), methodFieldName); } /** * Generate this method, including the code and exception table entry. */ - private void generateMethod(ClassBuilder clb, ClassDesc className) { - MethodTypeDesc desc = MethodTypeDesc.of(toClassDesc(returnType), - Arrays.stream(parameterTypes).map(ProxyGenerator::toClassDesc).toArray(ClassDesc[]::new)); - 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 -> { - ConstantPoolBuilder cpb = mb.constantPool(); - List exceptionClassEntries = Arrays.asList(exceptionTypes) - .stream() - .map(ProxyGenerator::toClassDesc) - .map(cpb::classEntry) - .toList(); - mb.with(ExceptionsAttribute.of(exceptionClassEntries)); - mb.withCode(cob -> - cob.trying(tryb -> { - tryb.aload(tryb.receiverSlot()) - .getfield(CD_Proxy, handlerFieldName, CD_InvocationHandler) - .aload(tryb.receiverSlot()) - .getstatic(className, methodFieldName, CD_Method); - - if (parameterTypes.length > 0) { - // Create an array and fill with the parameters converting primitives to wrappers - tryb.constantInstruction(parameterTypes.length) - .anewarray(CD_Object); - for (int i = 0; i < parameterTypes.length; i++) { - tryb.dup() - .constantInstruction(i); - codeWrapArgument(tryb, parameterTypes[i], tryb.parameterSlot(i)); - tryb.aastore(); - } - } else { - tryb.aconst_null(); - } + private void generateMethod(ClassWriter cw, String className) { + MethodType mt = MethodType.methodType(returnType, parameterTypes); + String desc = mt.toMethodDescriptorString(); + int accessFlags = ACC_PUBLIC | ACC_FINAL; + if (method.isVarArgs()) accessFlags |= ACC_VARARGS; + + MethodVisitor mv = cw.visitMethod(accessFlags, + method.getName(), desc, null, + typeNames(Arrays.asList(exceptionTypes))); + + int[] parameterSlot = new int[parameterTypes.length]; + int nextSlot = 1; + for (int i = 0; i < parameterSlot.length; i++) { + parameterSlot[i] = nextSlot; + nextSlot += getWordsPerType(parameterTypes[i]); + } - tryb.invokeinterface(CD_InvocationHandler, "invoke", MTD_Object_Object_Method_ObjectArray); + mv.visitCode(); + Label L_startBlock = new Label(); + Label L_endBlock = new Label(); + Label L_RuntimeHandler = new Label(); + Label L_ThrowableHandler = new Label(); + + List> catchList = computeUniqueCatchList(exceptionTypes); + if (catchList.size() > 0) { + for (Class ex : catchList) { + mv.visitTryCatchBlock(L_startBlock, L_endBlock, L_RuntimeHandler, + dotToSlash(ex.getName())); + } - if (returnType == void.class) { - tryb.pop() - .return_(); - } else { - codeUnwrapReturnValue(tryb, returnType); - } - }, catchBuilder -> { - if (!catchList.isEmpty()) { - catchBuilder.catchingMulti(catchList, ehb -> ehb - .athrow()); // just rethrow the exception - - catchBuilder.catching(CD_Throwable, ehb -> ehb - .new_(CD_UndeclaredThrowableException) - .dup_x1() - .swap() - .invokespecial(CD_UndeclaredThrowableException, INIT_NAME, MTD_void_Throwable) - .athrow()); - } - })); - }); + mv.visitTryCatchBlock(L_startBlock, L_endBlock, L_ThrowableHandler, + JL_THROWABLE); + } + mv.visitLabel(L_startBlock); + + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, JLR_PROXY, handlerFieldName, + LJLR_INVOCATION_HANDLER); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETSTATIC, dotToSlash(className), methodFieldName, + LJLR_METHOD); + + if (parameterTypes.length > 0) { + // Create an array and fill with the parameters converting primitives to wrappers + emitIconstInsn(mv, parameterTypes.length); + mv.visitTypeInsn(Opcodes.ANEWARRAY, JL_OBJECT); + for (int i = 0; i < parameterTypes.length; i++) { + mv.visitInsn(DUP); + emitIconstInsn(mv, i); + codeWrapArgument(mv, parameterTypes[i], parameterSlot[i]); + mv.visitInsn(Opcodes.AASTORE); + } + } else { + mv.visitInsn(Opcodes.ACONST_NULL); + } + + mv.visitMethodInsn(INVOKEINTERFACE, JLR_INVOCATION_HANDLER, + "invoke", + "(Ljava/lang/Object;Ljava/lang/reflect/Method;" + + "[Ljava/lang/Object;)Ljava/lang/Object;", true); + + if (returnType == void.class) { + mv.visitInsn(POP); + mv.visitInsn(RETURN); + } else { + codeUnwrapReturnValue(mv, returnType); + } + + mv.visitLabel(L_endBlock); + + // Generate exception handler + mv.visitLabel(L_RuntimeHandler); + mv.visitInsn(ATHROW); // just rethrow the exception + + mv.visitLabel(L_ThrowableHandler); + mv.visitVarInsn(ASTORE, 1); + mv.visitTypeInsn(Opcodes.NEW, JLR_UNDECLARED_THROWABLE_EX); + mv.visitInsn(DUP); + mv.visitVarInsn(ALOAD, 1); + mv.visitMethodInsn(INVOKESPECIAL, JLR_UNDECLARED_THROWABLE_EX, + "", "(Ljava/lang/Throwable;)V", false); + mv.visitInsn(ATHROW); + // Maxs computed by ClassWriter.COMPUTE_FRAMES, these arguments ignored + mv.visitMaxs(-1, -1); + mv.visitEnd(); } /** @@ -699,13 +816,15 @@ private void generateMethod(ClassBuilder clb, ClassDesc className) { * index, in order for it to be passed (as an Object) to the * invocation handler's "invoke" method. */ - private void codeWrapArgument(CodeBuilder cob, Class type, int slot) { + private void codeWrapArgument(MethodVisitor mv, Class type, int slot) { if (type.isPrimitive()) { - cob.loadInstruction(TypeKind.from(type).asLoadable(), slot); PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(type); - cob.invokestatic(prim.wrapperClass, "valueOf", prim.wrapperValueOf); + + mv.visitVarInsn(prim.loadOpcode, slot); + mv.visitMethodInsn(INVOKESTATIC, prim.wrapperClassName, "valueOf", + prim.wrapperValueOfDesc, false); } else { - cob.aload(slot); + mv.visitVarInsn(ALOAD, slot); } } @@ -714,16 +833,19 @@ private void codeWrapArgument(CodeBuilder cob, Class type, int slot) { * type from the invocation handler's "invoke" method (as type * Object) to its correct type. */ - private void codeUnwrapReturnValue(CodeBuilder cob, Class type) { + private void codeUnwrapReturnValue(MethodVisitor mv, Class type) { if (type.isPrimitive()) { PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(type); - cob.typeCheckInstruction(Opcode.CHECKCAST, prim.wrapperClass) - .invokevirtual(prim.wrapperClass, prim.unwrapMethodName, prim.unwrapMethodType) - .returnInstruction(TypeKind.from(type).asLoadable()); + mv.visitTypeInsn(CHECKCAST, prim.wrapperClassName); + mv.visitMethodInsn(INVOKEVIRTUAL, + prim.wrapperClassName, + prim.unwrapMethodName, prim.unwrapMethodDesc, false); + + mv.visitInsn(prim.returnOpcode); } else { - cob.checkcast(toClassDesc(type)) - .areturn(); + mv.visitTypeInsn(CHECKCAST, dotToSlash(type.getName())); + mv.visitInsn(ARETURN); } } @@ -732,28 +854,40 @@ private void codeUnwrapReturnValue(CodeBuilder cob, Class type) { * the Method object for this proxy method. A class loader is * anticipated at local variable index 0. */ - private void codeFieldInitialization(CodeBuilder cob, ClassDesc className) { - codeClassForName(cob, fromClass); + private void codeFieldInitialization(MethodVisitor mv, String className) { + codeClassForName(mv, fromClass); + + mv.visitLdcInsn(method.getName()); + + emitIconstInsn(mv, parameterTypes.length); - cob.ldc(method.getName()) - .constantInstruction(parameterTypes.length) - .anewarray(CD_Class); + mv.visitTypeInsn(Opcodes.ANEWARRAY, JL_CLASS); // Construct an array with the parameter types mapping primitives to Wrapper types for (int i = 0; i < parameterTypes.length; i++) { - cob.dup() - .constantInstruction(i); + mv.visitInsn(DUP); + emitIconstInsn(mv, i); + if (parameterTypes[i].isPrimitive()) { - PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(parameterTypes[i]); - cob.getstatic(prim.wrapperClass, "TYPE", CD_Class); + PrimitiveTypeInfo prim = + PrimitiveTypeInfo.get(parameterTypes[i]); + mv.visitFieldInsn(GETSTATIC, + prim.wrapperClassName, "TYPE", LJL_CLASS); } else { - codeClassForName(cob, parameterTypes[i]); + codeClassForName(mv, parameterTypes[i]); } - cob.aastore(); + mv.visitInsn(Opcodes.AASTORE); } // lookup the method - cob.invokevirtual(CD_Class, "getMethod", MTD_Method_String_ClassArray) - .putstatic(className, methodFieldName, CD_Method); + mv.visitMethodInsn(INVOKEVIRTUAL, + JL_CLASS, + "getMethod", + "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", + false); + + mv.visitFieldInsn(PUTSTATIC, + dotToSlash(className), + methodFieldName, LJLR_METHOD); } /* @@ -767,11 +901,33 @@ private void codeFieldInitialization(CodeBuilder cob, ClassDesc className) { * 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.constantInstruction(Opcode.LDC, cl.getName()) - .iconst_0() // false - .aload(0)// classLoader - .invokestatic(CD_Class, "forName", MTD_Class_String_boolean_ClassLoader); + private void codeClassForName(MethodVisitor mv, Class cl) { + mv.visitLdcInsn(cl.getName()); + mv.visitInsn(ICONST_0); // false + mv.visitVarInsn(ALOAD, 0); // classLoader + mv.visitMethodInsn(INVOKESTATIC, + JL_CLASS, + "forName", + "(Ljava/lang/String;Z" + LJL_CLASSLOADER + ")Ljava/lang/Class;", + false); + } + + /** + * Visit a bytecode for a constant. + * + * @param mv The MethodVisitor + * @param cst The constant value + */ + private void emitIconstInsn(MethodVisitor mv, final 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) { + mv.visitIntInsn(Opcodes.SIPUSH, cst); + } else { + mv.visitLdcInsn(cst); + } } @Override @@ -786,23 +942,23 @@ public String toString() { * primitive type can be obtained using the static "get" method. */ private enum PrimitiveTypeInfo { - BYTE(byte.class, CD_byte, CD_Byte), - CHAR(char.class, CD_char, CD_Character), - DOUBLE(double.class, CD_double, CD_Double), - FLOAT(float.class, CD_float, CD_Float), - INT(int.class, CD_int, CD_Integer), - LONG(long.class, CD_long, CD_Long), - SHORT(short.class, CD_short, CD_Short), - BOOLEAN(boolean.class, CD_boolean, CD_Boolean); + BYTE(byte.class, ILOAD, IRETURN), + CHAR(char.class, ILOAD, IRETURN), + DOUBLE(double.class, DLOAD, DRETURN), + FLOAT(float.class, FLOAD, FRETURN), + INT(int.class, ILOAD, IRETURN), + LONG(long.class, LLOAD, LRETURN), + SHORT(short.class, ILOAD, IRETURN), + BOOLEAN(boolean.class, ILOAD, IRETURN); /** * internal name of corresponding wrapper class */ - private final ClassDesc wrapperClass; + private final String wrapperClassName; /** * method descriptor for wrapper class "valueOf" factory method */ - private final MethodTypeDesc wrapperValueOf; + private final String wrapperValueOfDesc; /** * name of wrapper class method for retrieving primitive value */ @@ -810,14 +966,31 @@ private enum PrimitiveTypeInfo { /** * descriptor of same method */ - private final MethodTypeDesc unwrapMethodType; - - PrimitiveTypeInfo(Class primitiveClass, ClassDesc baseType, ClassDesc wrapperClass) { - assert baseType.isPrimitive(); - this.wrapperClass = wrapperClass; - this.wrapperValueOf = MethodTypeDesc.of(wrapperClass, baseType); - this.unwrapMethodName = primitiveClass.getName() + "Value"; - this.unwrapMethodType = MethodTypeDesc.of(baseType); + private final String unwrapMethodDesc; + /** + * Load opcode used by this primitive + */ + private final int loadOpcode; + /** + * Return opcode used by this primitive + */ + private final int returnOpcode; + + PrimitiveTypeInfo(Class primitiveClass, int loadOpcode, int returnOpcode) { + assert primitiveClass.isPrimitive(); + assert returnOpcode - IRETURN == loadOpcode - ILOAD; + + Wrapper wrapper = Wrapper.forPrimitiveType(primitiveClass); + // single-char BaseType descriptor (see JVMS section 4.3.2) + String baseTypeString = wrapper.basicTypeString(); + var wrapperType = wrapper.wrapperType(); + wrapperClassName = dotToSlash(wrapperType.getName()); + wrapperValueOfDesc = + "(" + baseTypeString + ")" + wrapperType.descriptorString(); + unwrapMethodName = primitiveClass.getName() + "Value"; + unwrapMethodDesc = "()" + baseTypeString; + this.loadOpcode = loadOpcode; + this.returnOpcode = returnOpcode; } public static PrimitiveTypeInfo get(Class cl) { From c523c7beb18f71911ccdc853e344ee71d0888e8d Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Mon, 8 Jan 2024 17:01:59 +0100 Subject: [PATCH 15/23] improved StackMapDescoder::initFrameLocals performance --- .../classes/jdk/internal/classfile/impl/StackMapDecoder.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 0fdb3622d6b20..77354496a9e72 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 @@ -80,7 +80,8 @@ public static List initFrameLocals(ClassEntry thisClass, S } else { vtis = new VerificationTypeInfo[methodType.parameterCount()]; } - for(var arg : methodType.parameterList()) { + for(int pi = 0; pi < methodType.parameterCount(); pi++) { + var arg = methodType.parameterType(pi); vtis[i++] = switch (arg.descriptorString().charAt(0)) { case 'I', 'S', 'C' ,'B', 'Z' -> SimpleVerificationTypeInfo.ITEM_INTEGER; case 'J' -> SimpleVerificationTypeInfo.ITEM_LONG; From 718af508d56578cf47959e0714736eb9ac6ac683 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Mon, 8 Jan 2024 17:09:37 +0100 Subject: [PATCH 16/23] applied suggested changes --- .../classes/jdk/internal/classfile/impl/StackCounter.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java b/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java index fe6042eaf79a0..30a51ae387ade 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java @@ -105,7 +105,7 @@ public StackCounter(LabelContext labelContext, maxStack = stack = rets = 0; for (var h : handlers) targets.add(new Target(labelContext.labelToBci(h.handler), 1)); maxLocals = isStatic ? 0 : 1; - maxLocals += countMethodStack(methodDesc, false); + maxLocals += countMethodSlots(methodDesc, false); bcs = new RawBytecodeHelper(bytecode); visited = new BitSet(bcs.endBci); targets.add(new Target(0, 0)); @@ -303,7 +303,7 @@ public StackCounter(LabelContext labelContext, case INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC, INVOKEINTERFACE, INVOKEDYNAMIC -> { var cpe = cp.entryByIndex(bcs.getIndexU2()); var nameAndType = opcode == INVOKEDYNAMIC ? ((DynamicConstantPoolEntry)cpe).nameAndType() : ((MemberRefEntry)cpe).nameAndType(); - addStackSlot(-countMethodStack(nameAndType.type(), true)); + addStackSlot(-countMethodSlots(nameAndType.type(), true)); if (opcode != INVOKESTATIC && opcode != INVOKEDYNAMIC) { addStackSlot(-1); } @@ -335,7 +335,7 @@ public StackCounter(LabelContext labelContext, maxStack += rets * maxStack; } - private static int countMethodStack(Utf8Entry descriptor, boolean subReturn) { + private static int countMethodSlots(Utf8Entry descriptor, boolean subReturn) { int cur = 0, end = descriptor.length(); if (cur >= end || descriptor.charAt(cur) != '(') throw new IllegalArgumentException("Bad method descriptor: " + descriptor); From bbbbbde0060869844f1f7ddff8c8c30b493ee1e8 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Mon, 8 Jan 2024 17:14:17 +0100 Subject: [PATCH 17/23] applied suggested changes --- .../classes/jdk/internal/classfile/impl/StackCounter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java b/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java index 30a51ae387ade..ae06d457de6e4 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java @@ -335,7 +335,7 @@ public StackCounter(LabelContext labelContext, maxStack += rets * maxStack; } - private static int countMethodSlots(Utf8Entry descriptor, boolean subReturn) { + private static int countMethodSlots(Utf8Entry descriptor, boolean asStackDelta) { int cur = 0, end = descriptor.length(); if (cur >= end || descriptor.charAt(cur) != '(') throw new IllegalArgumentException("Bad method descriptor: " + descriptor); @@ -362,7 +362,7 @@ private static int countMethodSlots(Utf8Entry descriptor, boolean subReturn) { } case ')' -> { if (cur < end) { - if (subReturn) { + if (asStackDelta) { return switch (descriptor.charAt(cur++)) { case 'Z', 'B', 'C', 'S', 'I', 'F', '[', 'L' -> count - 1; case 'J', 'D' -> count - 2; From 7939417082d640c87afafc0665cf56e1071c12f8 Mon Sep 17 00:00:00 2001 From: Adam Sotona <10807609+asotona@users.noreply.github.com> Date: Tue, 9 Jan 2024 09:20:32 +0100 Subject: [PATCH 18/23] Update src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java Co-authored-by: liach <7806504+liach@users.noreply.github.com> --- .../classes/jdk/internal/classfile/impl/StackCounter.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java b/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java index ae06d457de6e4..52a78340c9fcb 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java @@ -353,7 +353,9 @@ private static int countMethodSlots(Utf8Entry descriptor, boolean asStackDelta) inArray = false; } case 'L' -> { - while (cur < end && descriptor.charAt(cur++) != ';'); + cur = descriptor.indexOf(';', cur) + 1; + if (cur == 0) + throw new IllegalArgumentException("Bad method descriptor: " + descriptor); count++; inArray = false; } From f281e0a7567729265034648e76008d9eefaee357 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Tue, 9 Jan 2024 14:18:07 +0100 Subject: [PATCH 19/23] improved and extended GenerateStackMaps benchmarks and renamed to CodeAttributeTools --- ...StackMaps.java => CodeAttributeTools.java} | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) rename test/micro/org/openjdk/bench/jdk/classfile/{GenerateStackMaps.java => CodeAttributeTools.java} (83%) diff --git a/test/micro/org/openjdk/bench/jdk/classfile/GenerateStackMaps.java b/test/micro/org/openjdk/bench/jdk/classfile/CodeAttributeTools.java similarity index 83% rename from test/micro/org/openjdk/bench/jdk/classfile/GenerateStackMaps.java rename to test/micro/org/openjdk/bench/jdk/classfile/CodeAttributeTools.java index 6fcf801c2cf4b..85bb927c7df16 100644 --- a/test/micro/org/openjdk/bench/jdk/classfile/GenerateStackMaps.java +++ b/test/micro/org/openjdk/bench/jdk/classfile/CodeAttributeTools.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 @@ -24,7 +24,6 @@ import java.io.IOException; import java.lang.constant.ClassDesc; -import java.lang.constant.MethodTypeDesc; import java.net.URI; import java.nio.ByteBuffer; import java.nio.file.FileSystems; @@ -34,12 +33,16 @@ import java.util.List; import java.lang.classfile.ClassFile; import java.lang.classfile.ClassReader; +import java.lang.classfile.MethodModel; +import java.lang.classfile.constantpool.ConstantPool; import java.lang.classfile.constantpool.ConstantPoolBuilder; +import java.lang.constant.MethodTypeDesc; import jdk.internal.classfile.impl.AbstractPseudoInstruction; import jdk.internal.classfile.impl.CodeImpl; import jdk.internal.classfile.impl.LabelContext; import jdk.internal.classfile.impl.ClassFileImpl; import jdk.internal.classfile.impl.SplitConstantPool; +import jdk.internal.classfile.impl.StackCounter; import jdk.internal.classfile.impl.StackMapGenerator; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; @@ -58,8 +61,8 @@ "--enable-preview", "--add-exports", "java.base/jdk.internal.classfile.impl=ALL-UNNAMED"}) @Warmup(iterations = 2) -@Measurement(iterations = 10) -public class GenerateStackMaps { +@Measurement(iterations = 8) +public class CodeAttributeTools { record GenData(LabelContext labelContext, ClassDesc thisClass, @@ -71,17 +74,13 @@ record GenData(LabelContext labelContext, List handlers) {} List data; - Iterator it; - GenData d; - ClassFile cc; - @Setup(Level.Trial) + @Setup(Level.Invocation) public void setup() throws IOException { - cc = ClassFile.of(); data = new ArrayList<>(); Files.walk(FileSystems.getFileSystem(URI.create("jrt:/")).getPath("modules/java.base/java")).forEach(p -> { if (Files.isRegularFile(p) && p.toString().endsWith(".class")) try { - var clm = cc.parse(p); + var clm = ClassFile.of().parse(p); var thisCls = clm.thisClass().asSymbol(); var cp = new SplitConstantPool((ClassReader)clm.constantPool()); for (var m : clm.methods()) { @@ -105,11 +104,8 @@ public void setup() throws IOException { } @Benchmark - public void benchmark() { - if (it == null || !it.hasNext()) - it = data.iterator(); - var d = it.next(); - new StackMapGenerator( + public void benchmarkStackMapsGenerator() { + for (var d : data) new StackMapGenerator( d.labelContext(), d.thisClass(), d.methodName(), @@ -117,7 +113,19 @@ public void benchmark() { d.isStatic(), d.bytecode().rewind(), (SplitConstantPool)d.constantPool(), - (ClassFileImpl)cc, + (ClassFileImpl)ClassFile.of(), + d.handlers()); + } + + @Benchmark + public void benchmarkStackCounter() { + for (var d : data) new StackCounter( + d.labelContext(), + d.methodName(), + d.methodDesc(), + d.isStatic(), + d.bytecode().rewind(), + (SplitConstantPool)d.constantPool(), d.handlers()); } } From e567029ae963ccb3fdeaabaecd98dd7e3aaf8c00 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Tue, 9 Jan 2024 14:22:13 +0100 Subject: [PATCH 20/23] reverted custom method slots counting in StackCounter --- .../internal/classfile/impl/StackCounter.java | 67 +++---------------- 1 file changed, 10 insertions(+), 57 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java b/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java index 52a78340c9fcb..bc15e481d57d0 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java @@ -33,7 +33,6 @@ import java.lang.classfile.constantpool.DynamicConstantPoolEntry; import java.lang.classfile.constantpool.MemberRefEntry; import static java.lang.classfile.ClassFile.*; -import java.lang.classfile.constantpool.Utf8Entry; import java.lang.constant.MethodTypeDesc; import java.util.ArrayDeque; import java.util.Queue; @@ -46,8 +45,8 @@ private record Target(int bci, int stack) {} static StackCounter of(DirectCodeBuilder dcb, BufWriterImpl buf) { return new StackCounter( dcb, - dcb.methodInfo.methodName(), - dcb.methodInfo.methodType(), + dcb.methodInfo.methodName().stringValue(), + dcb.methodInfo.methodTypeSymbol(), (dcb.methodInfo.methodFlags() & ACC_STATIC) != 0, dcb.bytecodesBufWriter.asByteBuffer().slice(0, dcb.bytecodesBufWriter.size()), dcb.constantPool, @@ -57,8 +56,8 @@ static StackCounter of(DirectCodeBuilder dcb, BufWriterImpl buf) { private int stack, maxStack, maxLocals, rets; private final RawBytecodeHelper bcs; - private final Utf8Entry methodName; - private final Utf8Entry methodDesc; + private final String methodName; + private final MethodTypeDesc methodDesc; private final SplitConstantPool cp; private final Queue targets; private final BitSet visited; @@ -92,8 +91,8 @@ private boolean next() { } public StackCounter(LabelContext labelContext, - Utf8Entry methodName, - Utf8Entry methodDesc, + String methodName, + MethodTypeDesc methodDesc, boolean isStatic, ByteBuffer bytecode, SplitConstantPool cp, @@ -105,7 +104,7 @@ public StackCounter(LabelContext labelContext, maxStack = stack = rets = 0; for (var h : handlers) targets.add(new Target(labelContext.labelToBci(h.handler), 1)); maxLocals = isStatic ? 0 : 1; - maxLocals += countMethodSlots(methodDesc, false); + maxLocals += Util.parameterSlots(methodDesc); bcs = new RawBytecodeHelper(bytecode); visited = new BitSet(bcs.endBci); targets.add(new Target(0, 0)); @@ -303,7 +302,8 @@ public StackCounter(LabelContext labelContext, case INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC, INVOKEINTERFACE, INVOKEDYNAMIC -> { var cpe = cp.entryByIndex(bcs.getIndexU2()); var nameAndType = opcode == INVOKEDYNAMIC ? ((DynamicConstantPoolEntry)cpe).nameAndType() : ((MemberRefEntry)cpe).nameAndType(); - addStackSlot(-countMethodSlots(nameAndType.type(), true)); + var mtd = Util.methodTypeSymbol(nameAndType); + addStackSlot(Util.slotSize(mtd.returnType()) - Util.parameterSlots(mtd)); if (opcode != INVOKESTATIC && opcode != INVOKEDYNAMIC) { addStackSlot(-1); } @@ -335,53 +335,6 @@ public StackCounter(LabelContext labelContext, maxStack += rets * maxStack; } - private static int countMethodSlots(Utf8Entry descriptor, boolean asStackDelta) { - int cur = 0, end = descriptor.length(); - if (cur >= end || descriptor.charAt(cur) != '(') - throw new IllegalArgumentException("Bad method descriptor: " + descriptor); - ++cur; // skip '(' - int count = 0; - boolean inArray = false; - while (cur < end) { - switch (descriptor.charAt(cur++)) { - case 'Z', 'B', 'C', 'S', 'I', 'F' -> { - count++; - inArray = false; - } - case 'J', 'D' -> { - count += inArray ? 1 : 2; - inArray = false; - } - case 'L' -> { - cur = descriptor.indexOf(';', cur) + 1; - if (cur == 0) - throw new IllegalArgumentException("Bad method descriptor: " + descriptor); - count++; - inArray = false; - } - case '[' -> { - inArray = true; - } - case ')' -> { - if (cur < end) { - if (asStackDelta) { - return switch (descriptor.charAt(cur++)) { - case 'Z', 'B', 'C', 'S', 'I', 'F', '[', 'L' -> count - 1; - case 'J', 'D' -> count - 2; - case 'V' -> count; - default -> throw new IllegalArgumentException("Bad method descriptor: " + descriptor); - }; - } else { - return count; - } - } - } - default -> throw new IllegalArgumentException("Bad method descriptor: " + descriptor); - } - } - throw new IllegalArgumentException("Bad method descriptor: " + descriptor); - } - /** * Calculated maximum number of the locals required * @return maximum number of the locals required @@ -416,6 +369,6 @@ private void error(String msg) { msg, bcs.bci, methodName, - MethodTypeDesc.ofDescriptor(methodDesc.stringValue()).displayDescriptor())); + methodDesc.displayDescriptor())); } } From 5f67f8c1fa61fb4790095bb980af35ffa0d26db0 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Tue, 9 Jan 2024 14:24:54 +0100 Subject: [PATCH 21/23] updated copyright year --- .../share/classes/jdk/internal/classfile/impl/StackCounter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java b/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java index bc15e481d57d0..85890aa0aed5f 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 From 5a04ec59f489805fd88a692443cd28d81846c4ac Mon Sep 17 00:00:00 2001 From: Adam Sotona <10807609+asotona@users.noreply.github.com> Date: Mon, 15 Jan 2024 12:58:24 +0100 Subject: [PATCH 22/23] Update src/java.base/share/classes/jdk/internal/classfile/impl/StackMapDecoder.java Co-authored-by: Andrey Turbanov --- .../classes/jdk/internal/classfile/impl/StackMapDecoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 77354496a9e72..75e9f7d817bca 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 @@ -80,7 +80,7 @@ public static List initFrameLocals(ClassEntry thisClass, S } else { vtis = new VerificationTypeInfo[methodType.parameterCount()]; } - for(int pi = 0; pi < methodType.parameterCount(); pi++) { + for (int pi = 0; pi < methodType.parameterCount(); pi++) { var arg = methodType.parameterType(pi); vtis[i++] = switch (arg.descriptorString().charAt(0)) { case 'I', 'S', 'C' ,'B', 'Z' -> SimpleVerificationTypeInfo.ITEM_INTEGER; From 2d5ddf6fe98e6a4a7f4c6c3f7db8782e3ec50c28 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Mon, 4 Mar 2024 12:23:24 +0100 Subject: [PATCH 23/23] applied suggested changes --- .../jdk/internal/classfile/impl/StackCounter.java | 8 ++++---- .../bench/jdk/classfile/CodeAttributeTools.java | 13 +++++++------ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java b/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java index 85890aa0aed5f..688af262c7053 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/StackCounter.java @@ -25,18 +25,18 @@ */ package jdk.internal.classfile.impl; -import java.nio.ByteBuffer; -import java.util.BitSet; -import java.util.List; import java.lang.classfile.TypeKind; import java.lang.classfile.constantpool.ConstantDynamicEntry; import java.lang.classfile.constantpool.DynamicConstantPoolEntry; import java.lang.classfile.constantpool.MemberRefEntry; -import static java.lang.classfile.ClassFile.*; import java.lang.constant.MethodTypeDesc; +import java.nio.ByteBuffer; import java.util.ArrayDeque; +import java.util.BitSet; +import java.util.List; import java.util.Queue; +import static java.lang.classfile.ClassFile.*; public final class StackCounter { diff --git a/test/micro/org/openjdk/bench/jdk/classfile/CodeAttributeTools.java b/test/micro/org/openjdk/bench/jdk/classfile/CodeAttributeTools.java index 85bb927c7df16..1a9d989c54601 100644 --- a/test/micro/org/openjdk/bench/jdk/classfile/CodeAttributeTools.java +++ b/test/micro/org/openjdk/bench/jdk/classfile/CodeAttributeTools.java @@ -54,6 +54,7 @@ import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; @BenchmarkMode(Mode.Throughput) @State(Scope.Benchmark) @@ -104,8 +105,8 @@ public void setup() throws IOException { } @Benchmark - public void benchmarkStackMapsGenerator() { - for (var d : data) new StackMapGenerator( + public void benchmarkStackMapsGenerator(Blackhole bh) { + for (var d : data) bh.consume(new StackMapGenerator( d.labelContext(), d.thisClass(), d.methodName(), @@ -114,18 +115,18 @@ public void benchmarkStackMapsGenerator() { d.bytecode().rewind(), (SplitConstantPool)d.constantPool(), (ClassFileImpl)ClassFile.of(), - d.handlers()); + d.handlers())); } @Benchmark - public void benchmarkStackCounter() { - for (var d : data) new StackCounter( + public void benchmarkStackCounter(Blackhole bh) { + for (var d : data) bh.consume(new StackCounter( d.labelContext(), d.methodName(), d.methodDesc(), d.isStatic(), d.bytecode().rewind(), (SplitConstantPool)d.constantPool(), - d.handlers()); + d.handlers())); } }