|
1 | 1 | /*
|
2 |
| - * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. |
| 2 | + * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. |
3 | 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
4 | 4 | *
|
5 | 5 | * This code is free software; you can redistribute it and/or modify it
|
|
27 | 27 |
|
28 | 28 | import jdk.internal.access.JavaLangAccess;
|
29 | 29 | import jdk.internal.access.SharedSecrets;
|
30 |
| -import jdk.internal.javac.PreviewFeature; |
| 30 | +import jdk.internal.misc.VM; |
| 31 | +import jdk.internal.org.objectweb.asm.ClassWriter; |
| 32 | +import jdk.internal.org.objectweb.asm.MethodVisitor; |
| 33 | +import jdk.internal.util.ClassFileDumper; |
31 | 34 | import jdk.internal.vm.annotation.Stable;
|
32 | 35 | import sun.invoke.util.Wrapper;
|
33 | 36 |
|
34 | 37 | import java.lang.invoke.MethodHandles.Lookup;
|
35 |
| -import java.util.ArrayList; |
36 |
| -import java.util.Iterator; |
37 |
| -import java.util.List; |
38 | 38 | import java.util.Objects;
|
| 39 | +import java.util.Set; |
39 | 40 |
|
| 41 | +import static java.lang.invoke.MethodHandles.Lookup.ClassOption.STRONG; |
40 | 42 | import static java.lang.invoke.MethodType.methodType;
|
| 43 | +import static jdk.internal.org.objectweb.asm.Opcodes.*; |
41 | 44 |
|
42 | 45 | /**
|
43 | 46 | * <p>Methods to facilitate the creation of String concatenation methods, that
|
|
98 | 101 | */
|
99 | 102 | public final class StringConcatFactory {
|
100 | 103 |
|
| 104 | + private static final int HIGH_ARITY_THRESHOLD; |
| 105 | + |
| 106 | + static { |
| 107 | + String highArity = VM.getSavedProperty("java.lang.invoke.StringConcat.highArityThreshold"); |
| 108 | + HIGH_ARITY_THRESHOLD = highArity != null ? Integer.parseInt(highArity) : 20; |
| 109 | + } |
| 110 | + |
101 | 111 | /**
|
102 | 112 | * Tag used to demarcate an ordinary argument.
|
103 | 113 | */
|
@@ -354,9 +364,14 @@ public static CallSite makeConcatWithConstants(MethodHandles.Lookup lookup,
|
354 | 364 | }
|
355 | 365 |
|
356 | 366 | try {
|
357 |
| - return new ConstantCallSite( |
358 |
| - generateMHInlineCopy(concatType, constantStrings) |
359 |
| - .viewAsType(concatType, true)); |
| 367 | + if (concatType.parameterCount() < HIGH_ARITY_THRESHOLD) { |
| 368 | + return new ConstantCallSite( |
| 369 | + generateMHInlineCopy(concatType, constantStrings) |
| 370 | + .viewAsType(concatType, true)); |
| 371 | + } else { |
| 372 | + return new ConstantCallSite( |
| 373 | + SimpleStringBuilderStrategy.generate(lookup, concatType, constantStrings)); |
| 374 | + } |
360 | 375 | } catch (Error e) {
|
361 | 376 | // Pass through any error
|
362 | 377 | throw e;
|
@@ -1032,4 +1047,235 @@ private StringConcatFactory() {
|
1032 | 1047 | // no instantiation
|
1033 | 1048 | }
|
1034 | 1049 |
|
| 1050 | + /** |
| 1051 | + * Bytecode StringBuilder strategy. |
| 1052 | + * |
| 1053 | + * <p>This strategy emits StringBuilder chains as similar as possible |
| 1054 | + * to what javac would. No exact sizing of parameters or estimates. |
| 1055 | + */ |
| 1056 | + private static final class SimpleStringBuilderStrategy { |
| 1057 | + static final int CLASSFILE_VERSION = 52; // JDK 8 |
| 1058 | + static final String METHOD_NAME = "concat"; |
| 1059 | + // ClassFileDumper replaced java.lang.invoke.ProxyClassDumper in JDK 21 |
| 1060 | + // -- see JDK-8304846 |
| 1061 | + static final ClassFileDumper DUMPER = |
| 1062 | + ClassFileDumper.getInstance("java.lang.invoke.StringConcatFactory.dump", "stringConcatClasses"); |
| 1063 | + |
| 1064 | + /** |
| 1065 | + * Ensure a capacity in the initial StringBuilder to accommodate all |
| 1066 | + * constants plus this factor times the number of arguments. |
| 1067 | + */ |
| 1068 | + static final int ARGUMENT_SIZE_FACTOR = 4; |
| 1069 | + |
| 1070 | + static final Set<Lookup.ClassOption> SET_OF_STRONG = Set.of(STRONG); |
| 1071 | + |
| 1072 | + private SimpleStringBuilderStrategy() { |
| 1073 | + // no instantiation |
| 1074 | + } |
| 1075 | + |
| 1076 | + private static MethodHandle generate(Lookup lookup, MethodType args, String[] constants) throws Exception { |
| 1077 | + String className = getClassName(lookup.lookupClass()); |
| 1078 | + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES); |
| 1079 | + |
| 1080 | + cw.visit(CLASSFILE_VERSION, |
| 1081 | + ACC_SUPER + ACC_PUBLIC + ACC_FINAL + ACC_SYNTHETIC, |
| 1082 | + className, |
| 1083 | + null, |
| 1084 | + "java/lang/Object", |
| 1085 | + null |
| 1086 | + ); |
| 1087 | + |
| 1088 | + MethodVisitor mv = cw.visitMethod( |
| 1089 | + ACC_PUBLIC + ACC_STATIC + ACC_FINAL, |
| 1090 | + METHOD_NAME, |
| 1091 | + args.toMethodDescriptorString(), |
| 1092 | + null, |
| 1093 | + null); |
| 1094 | + |
| 1095 | + mv.visitCode(); |
| 1096 | + |
| 1097 | + |
| 1098 | + // Prepare StringBuilder instance |
| 1099 | + mv.visitTypeInsn(NEW, "java/lang/StringBuilder"); |
| 1100 | + mv.visitInsn(DUP); |
| 1101 | + |
| 1102 | + int len = 0; |
| 1103 | + for (String constant : constants) { |
| 1104 | + if (constant != null) { |
| 1105 | + len += constant.length(); |
| 1106 | + } |
| 1107 | + } |
| 1108 | + len += args.parameterCount() * ARGUMENT_SIZE_FACTOR; |
| 1109 | + iconst(mv, len); |
| 1110 | + mv.visitMethodInsn( |
| 1111 | + INVOKESPECIAL, |
| 1112 | + "java/lang/StringBuilder", |
| 1113 | + "<init>", |
| 1114 | + "(I)V", |
| 1115 | + false |
| 1116 | + ); |
| 1117 | + |
| 1118 | + // At this point, we have a blank StringBuilder on stack, fill it in with .append calls. |
| 1119 | + { |
| 1120 | + int off = 0; |
| 1121 | + for (int c = 0; c < args.parameterCount(); c++) { |
| 1122 | + if (constants[c] != null) { |
| 1123 | + mv.visitLdcInsn(constants[c]); |
| 1124 | + sbAppend(mv, "(Ljava/lang/String;)Ljava/lang/StringBuilder;"); |
| 1125 | + } |
| 1126 | + Class<?> cl = args.parameterType(c); |
| 1127 | + mv.visitVarInsn(getLoadOpcode(cl), off); |
| 1128 | + off += getParameterSize(cl); |
| 1129 | + String desc = getSBAppendDesc(cl); |
| 1130 | + sbAppend(mv, desc); |
| 1131 | + } |
| 1132 | + if (constants[constants.length - 1] != null) { |
| 1133 | + mv.visitLdcInsn(constants[constants.length - 1]); |
| 1134 | + sbAppend(mv, "(Ljava/lang/String;)Ljava/lang/StringBuilder;"); |
| 1135 | + } |
| 1136 | + } |
| 1137 | + |
| 1138 | + mv.visitMethodInsn( |
| 1139 | + INVOKEVIRTUAL, |
| 1140 | + "java/lang/StringBuilder", |
| 1141 | + "toString", |
| 1142 | + "()Ljava/lang/String;", |
| 1143 | + false |
| 1144 | + ); |
| 1145 | + |
| 1146 | + mv.visitInsn(ARETURN); |
| 1147 | + |
| 1148 | + mv.visitMaxs(-1, -1); |
| 1149 | + mv.visitEnd(); |
| 1150 | + cw.visitEnd(); |
| 1151 | + |
| 1152 | + byte[] classBytes = cw.toByteArray(); |
| 1153 | + try { |
| 1154 | + Lookup hiddenLookup = lookup.makeHiddenClassDefiner(className, classBytes, SET_OF_STRONG, DUMPER) |
| 1155 | + .defineClassAsLookup(true); |
| 1156 | + Class<?> innerClass = hiddenLookup.lookupClass(); |
| 1157 | + return hiddenLookup.findStatic(innerClass, METHOD_NAME, args); |
| 1158 | + } catch (Exception e) { |
| 1159 | + throw new StringConcatException("Exception while spinning the class", e); |
| 1160 | + } |
| 1161 | + } |
| 1162 | + |
| 1163 | + private static void sbAppend(MethodVisitor mv, String desc) { |
| 1164 | + mv.visitMethodInsn( |
| 1165 | + INVOKEVIRTUAL, |
| 1166 | + "java/lang/StringBuilder", |
| 1167 | + "append", |
| 1168 | + desc, |
| 1169 | + false |
| 1170 | + ); |
| 1171 | + } |
| 1172 | + |
| 1173 | + /** |
| 1174 | + * The generated class is in the same package as the host class as |
| 1175 | + * it's the implementation of the string concatenation for the host |
| 1176 | + * class. |
| 1177 | + */ |
| 1178 | + private static String getClassName(Class<?> hostClass) { |
| 1179 | + String name = hostClass.isHidden() ? hostClass.getName().replace('/', '_') |
| 1180 | + : hostClass.getName(); |
| 1181 | + return name.replace('.', '/') + "$$StringConcat"; |
| 1182 | + } |
| 1183 | + |
| 1184 | + private static String getSBAppendDesc(Class<?> cl) { |
| 1185 | + if (cl.isPrimitive()) { |
| 1186 | + if (cl == Integer.TYPE || cl == Byte.TYPE || cl == Short.TYPE) { |
| 1187 | + return "(I)Ljava/lang/StringBuilder;"; |
| 1188 | + } else if (cl == Boolean.TYPE) { |
| 1189 | + return "(Z)Ljava/lang/StringBuilder;"; |
| 1190 | + } else if (cl == Character.TYPE) { |
| 1191 | + return "(C)Ljava/lang/StringBuilder;"; |
| 1192 | + } else if (cl == Double.TYPE) { |
| 1193 | + return "(D)Ljava/lang/StringBuilder;"; |
| 1194 | + } else if (cl == Float.TYPE) { |
| 1195 | + return "(F)Ljava/lang/StringBuilder;"; |
| 1196 | + } else if (cl == Long.TYPE) { |
| 1197 | + return "(J)Ljava/lang/StringBuilder;"; |
| 1198 | + } else { |
| 1199 | + throw new IllegalStateException("Unhandled primitive StringBuilder.append: " + cl); |
| 1200 | + } |
| 1201 | + } else if (cl == String.class) { |
| 1202 | + return "(Ljava/lang/String;)Ljava/lang/StringBuilder;"; |
| 1203 | + } else { |
| 1204 | + return "(Ljava/lang/Object;)Ljava/lang/StringBuilder;"; |
| 1205 | + } |
| 1206 | + } |
| 1207 | + |
| 1208 | + private static String getStringValueOfDesc(Class<?> cl) { |
| 1209 | + if (cl.isPrimitive()) { |
| 1210 | + if (cl == Integer.TYPE || cl == Byte.TYPE || cl == Short.TYPE) { |
| 1211 | + return "(I)Ljava/lang/String;"; |
| 1212 | + } else if (cl == Boolean.TYPE) { |
| 1213 | + return "(Z)Ljava/lang/String;"; |
| 1214 | + } else if (cl == Character.TYPE) { |
| 1215 | + return "(C)Ljava/lang/String;"; |
| 1216 | + } else if (cl == Double.TYPE) { |
| 1217 | + return "(D)Ljava/lang/String;"; |
| 1218 | + } else if (cl == Float.TYPE) { |
| 1219 | + return "(F)Ljava/lang/String;"; |
| 1220 | + } else if (cl == Long.TYPE) { |
| 1221 | + return "(J)Ljava/lang/String;"; |
| 1222 | + } else { |
| 1223 | + throw new IllegalStateException("Unhandled String.valueOf: " + cl); |
| 1224 | + } |
| 1225 | + } else if (cl == String.class) { |
| 1226 | + return "(Ljava/lang/String;)Ljava/lang/String;"; |
| 1227 | + } else { |
| 1228 | + return "(Ljava/lang/Object;)Ljava/lang/String;"; |
| 1229 | + } |
| 1230 | + } |
| 1231 | + |
| 1232 | + /** |
| 1233 | + * The following method is copied from |
| 1234 | + * org.objectweb.asm.commons.InstructionAdapter. Part of ASM: a very small |
| 1235 | + * and fast Java bytecode manipulation framework. |
| 1236 | + * Copyright (c) 2000-2005 INRIA, France Telecom All rights reserved. |
| 1237 | + */ |
| 1238 | + private static void iconst(MethodVisitor mv, final int cst) { |
| 1239 | + if (cst >= -1 && cst <= 5) { |
| 1240 | + mv.visitInsn(ICONST_0 + cst); |
| 1241 | + } else if (cst >= Byte.MIN_VALUE && cst <= Byte.MAX_VALUE) { |
| 1242 | + mv.visitIntInsn(BIPUSH, cst); |
| 1243 | + } else if (cst >= Short.MIN_VALUE && cst <= Short.MAX_VALUE) { |
| 1244 | + mv.visitIntInsn(SIPUSH, cst); |
| 1245 | + } else { |
| 1246 | + mv.visitLdcInsn(cst); |
| 1247 | + } |
| 1248 | + } |
| 1249 | + |
| 1250 | + private static int getLoadOpcode(Class<?> c) { |
| 1251 | + if (c == Void.TYPE) { |
| 1252 | + throw new InternalError("Unexpected void type of load opcode"); |
| 1253 | + } |
| 1254 | + return ILOAD + getOpcodeOffset(c); |
| 1255 | + } |
| 1256 | + |
| 1257 | + private static int getOpcodeOffset(Class<?> c) { |
| 1258 | + if (c.isPrimitive()) { |
| 1259 | + if (c == Long.TYPE) { |
| 1260 | + return 1; |
| 1261 | + } else if (c == Float.TYPE) { |
| 1262 | + return 2; |
| 1263 | + } else if (c == Double.TYPE) { |
| 1264 | + return 3; |
| 1265 | + } |
| 1266 | + return 0; |
| 1267 | + } else { |
| 1268 | + return 4; |
| 1269 | + } |
| 1270 | + } |
| 1271 | + |
| 1272 | + private static int getParameterSize(Class<?> c) { |
| 1273 | + if (c == Void.TYPE) { |
| 1274 | + return 0; |
| 1275 | + } else if (c == Long.TYPE || c == Double.TYPE) { |
| 1276 | + return 2; |
| 1277 | + } |
| 1278 | + return 1; |
| 1279 | + } |
| 1280 | + } |
1035 | 1281 | }
|
0 commit comments