diff --git a/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java b/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java index 67e22c384ac..cd2f7ec170a 100644 --- a/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java +++ b/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java @@ -28,19 +28,25 @@ package java.lang.invoke; import jdk.internal.access.JavaLangAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.misc.VM; -import jdk.internal.org.objectweb.asm.ClassWriter; -import jdk.internal.org.objectweb.asm.MethodVisitor; import jdk.internal.util.ClassFileDumper; import jdk.internal.vm.annotation.Stable; import sun.invoke.util.Wrapper; +import java.lang.classfile.ClassBuilder; +import java.lang.classfile.ClassFile; +import java.lang.classfile.CodeBuilder; +import java.lang.classfile.TypeKind; +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.lang.constant.MethodTypeDesc; import java.lang.invoke.MethodHandles.Lookup; +import java.lang.reflect.AccessFlag; import java.util.Objects; import java.util.Set; +import java.util.function.Consumer; import static java.lang.invoke.MethodHandles.Lookup.ClassOption.STRONG; import static java.lang.invoke.MethodType.methodType; -import static jdk.internal.org.objectweb.asm.Opcodes.*; /** *

Methods to facilitate the creation of String concatenation methods, that @@ -364,7 +370,7 @@ public final class StringConcatFactory { } try { - if (concatType.parameterCount() < HIGH_ARITY_THRESHOLD) { + if (concatType.parameterCount() <= HIGH_ARITY_THRESHOLD) { return new ConstantCallSite( generateMHInlineCopy(concatType, constantStrings) .viewAsType(concatType, true)); @@ -1054,12 +1060,20 @@ public final class StringConcatFactory { * to what javac would. No exact sizing of parameters or estimates. */ private static final class SimpleStringBuilderStrategy { - static final int CLASSFILE_VERSION = 52; // JDK 8 static final String METHOD_NAME = "concat"; - // ClassFileDumper replaced java.lang.invoke.ProxyClassDumper in JDK 21 - // -- see JDK-8304846 + static final ClassDesc STRING_BUILDER = ClassDesc.ofDescriptor("Ljava/lang/StringBuilder;"); static final ClassFileDumper DUMPER = ClassFileDumper.getInstance("java.lang.invoke.StringConcatFactory.dump", "stringConcatClasses"); + static final MethodTypeDesc APPEND_BOOLEAN_TYPE = MethodTypeDesc.of(STRING_BUILDER, ConstantDescs.CD_boolean); + static final MethodTypeDesc APPEND_CHAR_TYPE = MethodTypeDesc.of(STRING_BUILDER, ConstantDescs.CD_char); + static final MethodTypeDesc APPEND_DOUBLE_TYPE = MethodTypeDesc.of(STRING_BUILDER, ConstantDescs.CD_double); + static final MethodTypeDesc APPEND_FLOAT_TYPE = MethodTypeDesc.of(STRING_BUILDER, ConstantDescs.CD_float); + static final MethodTypeDesc APPEND_INT_TYPE = MethodTypeDesc.of(STRING_BUILDER, ConstantDescs.CD_int); + static final MethodTypeDesc APPEND_LONG_TYPE = MethodTypeDesc.of(STRING_BUILDER, ConstantDescs.CD_long); + static final MethodTypeDesc APPEND_OBJECT_TYPE = MethodTypeDesc.of(STRING_BUILDER, ConstantDescs.CD_Object); + static final MethodTypeDesc APPEND_STRING_TYPE = MethodTypeDesc.of(STRING_BUILDER, ConstantDescs.CD_String); + static final MethodTypeDesc INT_CONSTRUCTOR_TYPE = MethodTypeDesc.of(ConstantDescs.CD_void, ConstantDescs.CD_int); + static final MethodTypeDesc TO_STRING_TYPE = MethodTypeDesc.of(ConstantDescs.CD_String); /** * Ensure a capacity in the initial StringBuilder to accommodate all @@ -1075,81 +1089,17 @@ public final class StringConcatFactory { private static MethodHandle generate(Lookup lookup, MethodType args, String[] constants) throws Exception { String className = getClassName(lookup.lookupClass()); - ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES); - cw.visit(CLASSFILE_VERSION, - ACC_SUPER + ACC_PUBLIC + ACC_FINAL + ACC_SYNTHETIC, - className, - null, - "java/lang/Object", - null - ); - - MethodVisitor mv = cw.visitMethod( - ACC_PUBLIC + ACC_STATIC + ACC_FINAL, - METHOD_NAME, - args.toMethodDescriptorString(), - null, - null); - - mv.visitCode(); - - - // Prepare StringBuilder instance - mv.visitTypeInsn(NEW, "java/lang/StringBuilder"); - mv.visitInsn(DUP); - - int len = 0; - for (String constant : constants) { - if (constant != null) { - len += constant.length(); - } - } - len += args.parameterCount() * ARGUMENT_SIZE_FACTOR; - iconst(mv, len); - mv.visitMethodInsn( - INVOKESPECIAL, - "java/lang/StringBuilder", - "", - "(I)V", - false - ); - - // At this point, we have a blank StringBuilder on stack, fill it in with .append calls. - { - int off = 0; - for (int c = 0; c < args.parameterCount(); c++) { - if (constants[c] != null) { - mv.visitLdcInsn(constants[c]); - sbAppend(mv, "(Ljava/lang/String;)Ljava/lang/StringBuilder;"); - } - Class cl = args.parameterType(c); - mv.visitVarInsn(getLoadOpcode(cl), off); - off += getParameterSize(cl); - String desc = getSBAppendDesc(cl); - sbAppend(mv, desc); - } - if (constants[constants.length - 1] != null) { - mv.visitLdcInsn(constants[constants.length - 1]); - sbAppend(mv, "(Ljava/lang/String;)Ljava/lang/StringBuilder;"); - } - } - - mv.visitMethodInsn( - INVOKEVIRTUAL, - "java/lang/StringBuilder", - "toString", - "()Ljava/lang/String;", - false - ); - - mv.visitInsn(ARETURN); - - mv.visitMaxs(-1, -1); - mv.visitEnd(); - cw.visitEnd(); - - byte[] classBytes = cw.toByteArray(); + byte[] classBytes = ClassFile.of().build(ClassDesc.of(className), + new Consumer() { + @Override + public void accept(ClassBuilder clb) { + clb.withFlags(AccessFlag.FINAL, AccessFlag.SUPER, AccessFlag.SYNTHETIC) + .withMethodBody(METHOD_NAME, + MethodTypeDesc.ofDescriptor(args.toMethodDescriptorString()), + ClassFile.ACC_FINAL | ClassFile.ACC_PRIVATE | ClassFile.ACC_STATIC, + generateMethod(constants, args)); + }}); try { Lookup hiddenLookup = lookup.makeHiddenClassDefiner(className, classBytes, SET_OF_STRONG, DUMPER) .defineClassAsLookup(true); @@ -1160,14 +1110,48 @@ public final class StringConcatFactory { } } - private static void sbAppend(MethodVisitor mv, String desc) { - mv.visitMethodInsn( - INVOKEVIRTUAL, - "java/lang/StringBuilder", - "append", - desc, - false - ); + private static Consumer generateMethod(String[] constants, MethodType args) { + return new Consumer() { + @Override + public void accept(CodeBuilder cb) { + cb.new_(STRING_BUILDER); + cb.dup(); + + int len = 0; + for (String constant : constants) { + if (constant != null) { + len += constant.length(); + } + } + len += args.parameterCount() * ARGUMENT_SIZE_FACTOR; + cb.constantInstruction(len); + cb.invokespecial(STRING_BUILDER, "", INT_CONSTRUCTOR_TYPE); + + // At this point, we have a blank StringBuilder on stack, fill it in with .append calls. + { + int off = 0; + for (int c = 0; c < args.parameterCount(); c++) { + if (constants[c] != null) { + cb.ldc(constants[c]); + cb.invokevirtual(STRING_BUILDER, "append", APPEND_STRING_TYPE); + } + Class cl = args.parameterType(c); + TypeKind kind = TypeKind.from(cl); + cb.loadInstruction(kind, off); + off += kind.slotSize(); + MethodTypeDesc desc = getSBAppendDesc(cl); + cb.invokevirtual(STRING_BUILDER, "append", desc); + } + if (constants[constants.length - 1] != null) { + cb.ldc(constants[constants.length - 1]); + cb.invokevirtual(STRING_BUILDER, "append", APPEND_STRING_TYPE); + } + } + + cb.invokevirtual(STRING_BUILDER, "toString", TO_STRING_TYPE); + cb.areturn(); + } + }; } /** @@ -1178,104 +1162,31 @@ public final class StringConcatFactory { private static String getClassName(Class hostClass) { String name = hostClass.isHidden() ? hostClass.getName().replace('/', '_') : hostClass.getName(); - return name.replace('.', '/') + "$$StringConcat"; + return name + "$$StringConcat"; } - private static String getSBAppendDesc(Class cl) { + private static MethodTypeDesc getSBAppendDesc(Class cl) { if (cl.isPrimitive()) { if (cl == Integer.TYPE || cl == Byte.TYPE || cl == Short.TYPE) { - return "(I)Ljava/lang/StringBuilder;"; + return APPEND_INT_TYPE; } else if (cl == Boolean.TYPE) { - return "(Z)Ljava/lang/StringBuilder;"; + return APPEND_BOOLEAN_TYPE; } else if (cl == Character.TYPE) { - return "(C)Ljava/lang/StringBuilder;"; + return APPEND_CHAR_TYPE; } else if (cl == Double.TYPE) { - return "(D)Ljava/lang/StringBuilder;"; + return APPEND_DOUBLE_TYPE; } else if (cl == Float.TYPE) { - return "(F)Ljava/lang/StringBuilder;"; + return APPEND_FLOAT_TYPE; } else if (cl == Long.TYPE) { - return "(J)Ljava/lang/StringBuilder;"; + return APPEND_LONG_TYPE; } else { throw new IllegalStateException("Unhandled primitive StringBuilder.append: " + cl); } } else if (cl == String.class) { - return "(Ljava/lang/String;)Ljava/lang/StringBuilder;"; + return APPEND_STRING_TYPE; } else { - return "(Ljava/lang/Object;)Ljava/lang/StringBuilder;"; + return APPEND_OBJECT_TYPE; } } - - private static String getStringValueOfDesc(Class cl) { - if (cl.isPrimitive()) { - if (cl == Integer.TYPE || cl == Byte.TYPE || cl == Short.TYPE) { - return "(I)Ljava/lang/String;"; - } else if (cl == Boolean.TYPE) { - return "(Z)Ljava/lang/String;"; - } else if (cl == Character.TYPE) { - return "(C)Ljava/lang/String;"; - } else if (cl == Double.TYPE) { - return "(D)Ljava/lang/String;"; - } else if (cl == Float.TYPE) { - return "(F)Ljava/lang/String;"; - } else if (cl == Long.TYPE) { - return "(J)Ljava/lang/String;"; - } else { - throw new IllegalStateException("Unhandled String.valueOf: " + cl); - } - } else if (cl == String.class) { - return "(Ljava/lang/String;)Ljava/lang/String;"; - } else { - return "(Ljava/lang/Object;)Ljava/lang/String;"; - } - } - - /** - * The following method is copied from - * org.objectweb.asm.commons.InstructionAdapter. Part of ASM: a very small - * and fast Java bytecode manipulation framework. - * Copyright (c) 2000-2005 INRIA, France Telecom All rights reserved. - */ - private static void iconst(MethodVisitor mv, final int cst) { - if (cst >= -1 && cst <= 5) { - mv.visitInsn(ICONST_0 + cst); - } else if (cst >= Byte.MIN_VALUE && cst <= Byte.MAX_VALUE) { - mv.visitIntInsn(BIPUSH, cst); - } else if (cst >= Short.MIN_VALUE && cst <= Short.MAX_VALUE) { - mv.visitIntInsn(SIPUSH, cst); - } else { - mv.visitLdcInsn(cst); - } - } - - private static int getLoadOpcode(Class c) { - if (c == Void.TYPE) { - throw new InternalError("Unexpected void type of load opcode"); - } - return ILOAD + getOpcodeOffset(c); - } - - private static int getOpcodeOffset(Class c) { - if (c.isPrimitive()) { - if (c == Long.TYPE) { - return 1; - } else if (c == Float.TYPE) { - return 2; - } else if (c == Double.TYPE) { - return 3; - } - return 0; - } else { - return 4; - } - } - - private static int getParameterSize(Class c) { - if (c == Void.TYPE) { - return 0; - } else if (c == Long.TYPE || c == Double.TYPE) { - return 2; - } - return 1; - } } } diff --git a/test/micro/org/openjdk/bench/java/lang/StringConcat.java b/test/micro/org/openjdk/bench/java/lang/StringConcat.java index c27514ec442..c4d2d1bfe11 100644 --- a/test/micro/org/openjdk/bench/java/lang/StringConcat.java +++ b/test/micro/org/openjdk/bench/java/lang/StringConcat.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -192,4 +192,31 @@ public class StringConcat { +f110 + ","+f111 + ","+f112 + ","+f113 + ","+f114 + ","+f115 + ","+f116 + ","+f117 + ","+f118 + ","+f119 + "," +f120 + ","+f121 + ","+f122; } + + @Benchmark + public String concat23StringConst() { + return f0 + f0 + f0 + f0 + f0 + f0 + f0 + f0 + f0 + f0 + f0 + f0 + f0 + f0 + f0 + f0 + f0 + f0 + f0 + f0 + f0 + f0 + f0 + """ + A really long constant string. Such as a copyright header: + * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + """; + } }