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 b918b756332..67e22c384ac 100644
--- a/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java
+++ b/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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
@@ -27,17 +27,20 @@ package java.lang.invoke;
import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.SharedSecrets;
-import jdk.internal.javac.PreviewFeature;
+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.invoke.MethodHandles.Lookup;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
import java.util.Objects;
+import java.util.Set;
+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
@@ -98,6 +101,13 @@ import static java.lang.invoke.MethodType.methodType;
*/
public final class StringConcatFactory {
+ private static final int HIGH_ARITY_THRESHOLD;
+
+ static {
+ String highArity = VM.getSavedProperty("java.lang.invoke.StringConcat.highArityThreshold");
+ HIGH_ARITY_THRESHOLD = highArity != null ? Integer.parseInt(highArity) : 20;
+ }
+
/**
* Tag used to demarcate an ordinary argument.
*/
@@ -354,9 +364,14 @@ public final class StringConcatFactory {
}
try {
- return new ConstantCallSite(
- generateMHInlineCopy(concatType, constantStrings)
- .viewAsType(concatType, true));
+ if (concatType.parameterCount() < HIGH_ARITY_THRESHOLD) {
+ return new ConstantCallSite(
+ generateMHInlineCopy(concatType, constantStrings)
+ .viewAsType(concatType, true));
+ } else {
+ return new ConstantCallSite(
+ SimpleStringBuilderStrategy.generate(lookup, concatType, constantStrings));
+ }
} catch (Error e) {
// Pass through any error
throw e;
@@ -1032,4 +1047,235 @@ public final class StringConcatFactory {
// no instantiation
}
+ /**
+ * Bytecode StringBuilder strategy.
+ *
+ *
This strategy emits StringBuilder chains as similar as possible
+ * 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 ClassFileDumper DUMPER =
+ ClassFileDumper.getInstance("java.lang.invoke.StringConcatFactory.dump", "stringConcatClasses");
+
+ /**
+ * Ensure a capacity in the initial StringBuilder to accommodate all
+ * constants plus this factor times the number of arguments.
+ */
+ static final int ARGUMENT_SIZE_FACTOR = 4;
+
+ static final Set SET_OF_STRONG = Set.of(STRONG);
+
+ private SimpleStringBuilderStrategy() {
+ // no instantiation
+ }
+
+ 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();
+ try {
+ Lookup hiddenLookup = lookup.makeHiddenClassDefiner(className, classBytes, SET_OF_STRONG, DUMPER)
+ .defineClassAsLookup(true);
+ Class> innerClass = hiddenLookup.lookupClass();
+ return hiddenLookup.findStatic(innerClass, METHOD_NAME, args);
+ } catch (Exception e) {
+ throw new StringConcatException("Exception while spinning the class", e);
+ }
+ }
+
+ private static void sbAppend(MethodVisitor mv, String desc) {
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "java/lang/StringBuilder",
+ "append",
+ desc,
+ false
+ );
+ }
+
+ /**
+ * The generated class is in the same package as the host class as
+ * it's the implementation of the string concatenation for the host
+ * class.
+ */
+ private static String getClassName(Class> hostClass) {
+ String name = hostClass.isHidden() ? hostClass.getName().replace('/', '_')
+ : hostClass.getName();
+ return name.replace('.', '/') + "$$StringConcat";
+ }
+
+ private static String getSBAppendDesc(Class> cl) {
+ if (cl.isPrimitive()) {
+ if (cl == Integer.TYPE || cl == Byte.TYPE || cl == Short.TYPE) {
+ return "(I)Ljava/lang/StringBuilder;";
+ } else if (cl == Boolean.TYPE) {
+ return "(Z)Ljava/lang/StringBuilder;";
+ } else if (cl == Character.TYPE) {
+ return "(C)Ljava/lang/StringBuilder;";
+ } else if (cl == Double.TYPE) {
+ return "(D)Ljava/lang/StringBuilder;";
+ } else if (cl == Float.TYPE) {
+ return "(F)Ljava/lang/StringBuilder;";
+ } else if (cl == Long.TYPE) {
+ return "(J)Ljava/lang/StringBuilder;";
+ } else {
+ throw new IllegalStateException("Unhandled primitive StringBuilder.append: " + cl);
+ }
+ } else if (cl == String.class) {
+ return "(Ljava/lang/String;)Ljava/lang/StringBuilder;";
+ } else {
+ return "(Ljava/lang/Object;)Ljava/lang/StringBuilder;";
+ }
+ }
+
+ 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 82254f56e79..c27514ec442 100644
--- a/test/micro/org/openjdk/bench/java/lang/StringConcat.java
+++ b/test/micro/org/openjdk/bench/java/lang/StringConcat.java
@@ -149,4 +149,47 @@ public class StringConcat {
return "string" + objectValue + objectValue + objectValue + objectValue + objectValue + objectValue;
}
+ private String
+ f0="1", f1="1", f2="1", f3="1", f4="1", f5="1", f6="1", f7="1", f8="1", f9="1",
+ f10="1", f11="1", f12="1", f13="1", f14="1", f15="1", f16="1", f17="1", f18="1", f19="1",
+ f20="1", f21="1", f22="1", f23="1", f24="1", f25="1", f26="1", f27="1", f28="1", f29="1",
+ f30="1", f31="1", f32="1", f33="1", f34="1", f35="1", f36="1", f37="1", f38="1", f39="1",
+ f40="1", f41="1", f42="1", f43="1", f44="1", f45="1", f46="1", f47="1", f48="1", f49="1",
+ f50="1", f51="1", f52="1", f53="1", f54="1", f55="1", f56="1", f57="1", f58="1", f59="1",
+ f60="1", f61="1", f62="1", f63="1", f64="1", f65="1", f66="1", f67="1", f68="1", f69="1",
+ f70="1", f71="1", f72="1", f73="1", f74="1", f75="1", f76="1", f77="1", f78="1", f79="1",
+ f80="1", f81="1", f82="1", f83="1", f84="1", f85="1", f86="1", f87="1", f88="1", f89="1",
+ f90="1", f91="1", f92="1", f93="1", f94="1", f95="1", f96="1", f97="1", f98="1", f99="1",
+ f100="1",f101="1",f102="1",f103="1",f104="1",f105="1",f106="1",f107="1",f108="1",f109="1",
+ f110="1",f111="1",f112="1",f113="1",f114="1",f115="1",f116="1",f117="1",f118="1",f119="1",
+ f120="1",f121="1",f122="1";
+
+ @Benchmark
+ public String concat13String() {
+ return f0 + ","+ f1 + ","+ f2 + ","+ f3 + ","+ f4 + ","+ f5 + ","+ f6 + ","+ f7 + ","+ f8 + ","+ f9 + ","
+ + f10 + ","+ f11 + ","+ f12;
+ }
+
+ @Benchmark
+ public String concat23String() {
+ return f0 + ","+ f1 + ","+ f2 + ","+ f3 + ","+ f4 + ","+ f5 + ","+ f6 + ","+ f7 + ","+ f8 + ","+ f9 + ","
+ + f10 + ","+ f11 + ","+ f12 + ","+ f13 + ","+ f14 + ","+ f15 + ","+ f16 + ","+ f17 + ","+ f18 + ","+ f19 + ","
+ + f20 + ","+ f21 + ","+ f22;
+ }
+ @Benchmark
+ public String concat123String() {
+ return f0 + ","+ f1 + ","+ f2 + ","+ f3 + ","+ f4 + ","+ f5 + ","+ f6 + ","+ f7 + ","+ f8 + ","+ f9 + ","
+ + f10 + ","+ f11 + ","+ f12 + ","+ f13 + ","+ f14 + ","+ f15 + ","+ f16 + ","+ f17 + ","+ f18 + ","+ f19 + ","
+ + f20 + ","+ f21 + ","+ f22 + ","+ f23 + ","+ f24 + ","+ f25 + ","+ f26 + ","+ f27 + ","+ f28 + ","+ f29 + ","
+ + f30 + ","+ f31 + ","+ f32 + ","+ f33 + ","+ f34 + ","+ f35 + ","+ f36 + ","+ f37 + ","+ f38 + ","+ f39 + ","
+ + f40 + ","+ f41 + ","+ f42 + ","+ f43 + ","+ f44 + ","+ f45 + ","+ f46 + ","+ f47 + ","+ f48 + ","+ f49 + ","
+ + f50 + ","+ f51 + ","+ f52 + ","+ f53 + ","+ f54 + ","+ f55 + ","+ f56 + ","+ f57 + ","+ f58 + ","+ f59 + ","
+ + f60 + ","+ f61 + ","+ f62 + ","+ f63 + ","+ f64 + ","+ f65 + ","+ f66 + ","+ f67 + ","+ f68 + ","+ f69 + ","
+ + f70 + ","+ f71 + ","+ f72 + ","+ f73 + ","+ f74 + ","+ f75 + ","+ f76 + ","+ f77 + ","+ f78 + ","+ f79 + ","
+ + f80 + ","+ f81 + ","+ f82 + ","+ f83 + ","+ f84 + ","+ f85 + ","+ f86 + ","+ f87 + ","+ f88 + ","+ f89 + ","
+ + f90 + ","+ f91 + ","+ f92 + ","+ f93 + ","+ f94 + ","+ f95 + ","+ f96 + ","+ f97 + ","+ f98 + ","+ f99 + ","
+ +f100 + ","+f101 + ","+f102 + ","+f103 + ","+f104 + ","+f105 + ","+f106 + ","+f107 + ","+f108 + ","+f109 + ","
+ +f110 + ","+f111 + ","+f112 + ","+f113 + ","+f114 + ","+f115 + ","+f116 + ","+f117 + ","+f118 + ","+f119 + ","
+ +f120 + ","+f121 + ","+f122;
+ }
}