1 /* 2 * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. 3 * Copyright (c) 2024, Alibaba Group Holding Limited. All Rights Reserved. 4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5 * 6 * This code is free software; you can redistribute it and/or modify it 7 * under the terms of the GNU General Public License version 2 only, as 8 * published by the Free Software Foundation. Oracle designates this 9 * particular file as subject to the "Classpath" exception as provided 10 * by Oracle in the LICENSE file that accompanied this code. 11 * 12 * This code is distributed in the hope that it will be useful, but WITHOUT 13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 * version 2 for more details (a copy is included in the LICENSE file that 16 * accompanied this code). 17 * 18 * You should have received a copy of the GNU General Public License version 19 * 2 along with this work; if not, write to the Free Software Foundation, 20 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 21 * 22 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 23 * or visit www.oracle.com if you need additional information or have any 24 * questions. 25 */ 26 27 package java.lang.invoke; 28 29 import jdk.internal.access.JavaLangAccess; 30 import jdk.internal.access.SharedSecrets; 31 import jdk.internal.constant.ConstantUtils; 32 import jdk.internal.constant.MethodTypeDescImpl; 33 import jdk.internal.constant.ReferenceClassDescImpl; 34 import jdk.internal.misc.VM; 35 import jdk.internal.util.ClassFileDumper; 36 import jdk.internal.util.ReferenceKey; 37 import jdk.internal.util.ReferencedKeyMap; 38 import jdk.internal.vm.annotation.Stable; 39 import sun.invoke.util.Wrapper; 40 41 import java.lang.classfile.Annotation; 42 import java.lang.classfile.ClassBuilder; 43 import java.lang.classfile.ClassFile; 44 import java.lang.classfile.CodeBuilder; 45 import java.lang.classfile.MethodBuilder; 46 import java.lang.classfile.TypeKind; 47 import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute; 48 import java.lang.constant.ClassDesc; 49 import java.lang.constant.MethodTypeDesc; 50 import java.lang.invoke.MethodHandles.Lookup; 51 import java.lang.ref.SoftReference; 52 import java.util.Map; 53 import java.util.Objects; 54 import java.util.Set; 55 import java.util.concurrent.ConcurrentHashMap; 56 import java.util.function.Consumer; 57 import java.util.function.Supplier; 58 59 import static java.lang.classfile.ClassFile.*; 60 import static java.lang.constant.ConstantDescs.*; 61 import static java.lang.invoke.MethodType.methodType; 62 63 /** 64 * <p>Methods to facilitate the creation of String concatenation methods, that 65 * can be used to efficiently concatenate a known number of arguments of known 66 * types, possibly after type adaptation and partial evaluation of arguments. 67 * These methods are typically used as <em>bootstrap methods</em> for {@code 68 * invokedynamic} call sites, to support the <em>string concatenation</em> 69 * feature of the Java Programming Language. 70 * 71 * <p>Indirect access to the behavior specified by the provided {@code 72 * MethodHandle} proceeds in order through two phases: 73 * 74 * <ol> 75 * <li><em>Linkage</em> occurs when the methods in this class are invoked. 76 * They take as arguments a method type describing the concatenated arguments 77 * count and types, and optionally the String <em>recipe</em>, plus the 78 * constants that participate in the String concatenation. The details on 79 * accepted recipe shapes are described further below. Linkage may involve 80 * dynamically loading a new class that implements the expected concatenation 81 * behavior. The {@code CallSite} holds the {@code MethodHandle} pointing to the 82 * exact concatenation method. The concatenation methods may be shared among 83 * different {@code CallSite}s, e.g. if linkage methods produce them as pure 84 * functions.</li> 85 * 86 * <li><em>Invocation</em> occurs when a generated concatenation method is 87 * invoked with the exact dynamic arguments. This may occur many times for a 88 * single concatenation method. The method referenced by the behavior {@code 89 * MethodHandle} is invoked with the static arguments and any additional dynamic 90 * arguments provided on invocation, as if by {@link MethodHandle#invoke(Object...)}.</li> 91 * </ol> 92 * 93 * <p> This class provides two forms of linkage methods: a simple version 94 * ({@link #makeConcat(java.lang.invoke.MethodHandles.Lookup, String, 95 * MethodType)}) using only the dynamic arguments, and an advanced version 96 * ({@link #makeConcatWithConstants(java.lang.invoke.MethodHandles.Lookup, 97 * String, MethodType, String, Object...)} using the advanced forms of capturing 98 * the constant arguments. The advanced strategy can produce marginally better 99 * invocation bytecode, at the expense of exploding the number of shapes of 100 * string concatenation methods present at runtime, because those shapes would 101 * include constant static arguments as well. 102 * 103 * @author Aleksey Shipilev 104 * @author Remi Forax 105 * @author Peter Levart 106 * 107 * @apiNote 108 * <p>There is a JVM limit (classfile structural constraint): no method 109 * can call with more than 255 slots. This limits the number of static and 110 * dynamic arguments one can pass to bootstrap method. Since there are potential 111 * concatenation strategies that use {@code MethodHandle} combinators, we need 112 * to reserve a few empty slots on the parameter lists to capture the 113 * temporal results. This is why bootstrap methods in this factory do not accept 114 * more than 200 argument slots. Users requiring more than 200 argument slots in 115 * concatenation are expected to split the large concatenation in smaller 116 * expressions. 117 * 118 * @since 9 119 */ 120 public final class StringConcatFactory { 121 private static final int HIGH_ARITY_THRESHOLD; 122 private static final int CACHE_THRESHOLD; 123 private static final int FORCE_INLINE_THRESHOLD; 124 125 static { 126 String highArity = VM.getSavedProperty("java.lang.invoke.StringConcat.highArityThreshold"); 127 HIGH_ARITY_THRESHOLD = highArity != null ? Integer.parseInt(highArity) : 0; 128 129 String cacheThreshold = VM.getSavedProperty("java.lang.invoke.StringConcat.cacheThreshold"); 130 CACHE_THRESHOLD = cacheThreshold != null ? Integer.parseInt(cacheThreshold) : 256; 131 132 String inlineThreshold = VM.getSavedProperty("java.lang.invoke.StringConcat.inlineThreshold"); 133 FORCE_INLINE_THRESHOLD = inlineThreshold != null ? Integer.parseInt(inlineThreshold) : 16; 134 } 135 136 /** 137 * Tag used to demarcate an ordinary argument. 138 */ 139 private static final char TAG_ARG = '\u0001'; 140 141 /** 142 * Tag used to demarcate a constant. 143 */ 144 private static final char TAG_CONST = '\u0002'; 145 146 /** 147 * Maximum number of argument slots in String Concat call. 148 * 149 * While the maximum number of argument slots that indy call can handle is 253, 150 * we do not use all those slots, to let the strategies with MethodHandle 151 * combinators to use some arguments. 152 */ 153 private static final int MAX_INDY_CONCAT_ARG_SLOTS = 200; 154 155 private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); 156 157 // StringConcatFactory bootstrap methods are startup sensitive, and may be 158 // special cased in java.lang.invoke.BootstrapMethodInvoker to ensure 159 // methods are invoked with exact type information to avoid generating 160 // code for runtime checks. Take care any changes or additions here are 161 // reflected there as appropriate. 162 163 /** 164 * Facilitates the creation of optimized String concatenation methods, that 165 * can be used to efficiently concatenate a known number of arguments of 166 * known types, possibly after type adaptation and partial evaluation of 167 * arguments. Typically used as a <em>bootstrap method</em> for {@code 168 * invokedynamic} call sites, to support the <em>string concatenation</em> 169 * feature of the Java Programming Language. 170 * 171 * <p>When the target of the {@code CallSite} returned from this method is 172 * invoked, it returns the result of String concatenation, taking all 173 * function arguments passed to the linkage method as inputs for 174 * concatenation. The target signature is given by {@code concatType}. 175 * For a target accepting: 176 * <ul> 177 * <li>zero inputs, concatenation results in an empty string;</li> 178 * <li>one input, concatenation results in the single 179 * input converted as per JLS {@jls 5.1.11} "String Conversion"; otherwise</li> 180 * <li>two or more inputs, the inputs are concatenated as per 181 * requirements stated in JLS {@jls 15.18.1} "String Concatenation Operator +". 182 * The inputs are converted as per JLS {@jls 5.1.11} "String Conversion", 183 * and combined from left to right.</li> 184 * </ul> 185 * 186 * <p>Assume the linkage arguments are as follows: 187 * 188 * <ul> 189 * <li>{@code concatType}, describing the {@code CallSite} signature</li> 190 * </ul> 191 * 192 * <p>Then the following linkage invariants must hold: 193 * 194 * <ul> 195 * <li>The number of parameter slots in {@code concatType} is 196 * less than or equal to 200</li> 197 * <li>The return type in {@code concatType} is assignable from {@link java.lang.String}</li> 198 * </ul> 199 * 200 * @param lookup Represents a lookup context with the accessibility 201 * privileges of the caller. Specifically, the lookup 202 * context must have 203 * {@linkplain MethodHandles.Lookup#hasFullPrivilegeAccess() 204 * full privilege access}. 205 * When used with {@code invokedynamic}, this is stacked 206 * automatically by the VM. 207 * @param name The name of the method to implement. This name is 208 * arbitrary, and has no meaning for this linkage method. 209 * When used with {@code invokedynamic}, this is provided by 210 * the {@code NameAndType} of the {@code InvokeDynamic} 211 * structure and is stacked automatically by the VM. 212 * @param concatType The expected signature of the {@code CallSite}. The 213 * parameter types represent the types of concatenation 214 * arguments; the return type is always assignable from {@link 215 * java.lang.String}. When used with {@code invokedynamic}, 216 * this is provided by the {@code NameAndType} of the {@code 217 * InvokeDynamic} structure and is stacked automatically by 218 * the VM. 219 * @return a CallSite whose target can be used to perform String 220 * concatenation, with dynamic concatenation arguments described by the given 221 * {@code concatType}. 222 * @throws StringConcatException If any of the linkage invariants described 223 * here are violated, or the lookup context 224 * does not have private access privileges. 225 * @throws NullPointerException If any of the incoming arguments is null. 226 * This will never happen when a bootstrap method 227 * is called with invokedynamic. 228 * 229 * @jls 5.1.11 String Conversion 230 * @jls 15.18.1 String Concatenation Operator + 231 */ 232 public static CallSite makeConcat(MethodHandles.Lookup lookup, 233 String name, 234 MethodType concatType) throws StringConcatException { 235 // This bootstrap method is unlikely to be used in practice, 236 // avoid optimizing it at the expense of makeConcatWithConstants 237 238 // Mock the recipe to reuse the concat generator code 239 String recipe = "\u0001".repeat(concatType.parameterCount()); 240 return makeConcatWithConstants(lookup, name, concatType, recipe); 241 } 242 243 /** 244 * Facilitates the creation of optimized String concatenation methods, that 245 * can be used to efficiently concatenate a known number of arguments of 246 * known types, possibly after type adaptation and partial evaluation of 247 * arguments. Typically used as a <em>bootstrap method</em> for {@code 248 * invokedynamic} call sites, to support the <em>string concatenation</em> 249 * feature of the Java Programming Language. 250 * 251 * <p>When the target of the {@code CallSite} returned from this method is 252 * invoked, it returns the result of String concatenation, taking all 253 * function arguments and constants passed to the linkage method as inputs for 254 * concatenation. The target signature is given by {@code concatType}, and 255 * does not include constants. 256 * For a target accepting: 257 * <ul> 258 * <li>zero inputs, concatenation results in an empty string;</li> 259 * <li>one input, concatenation results in the single 260 * input converted as per JLS {@jls 5.1.11} "String Conversion"; otherwise</li> 261 * <li>two or more inputs, the inputs are concatenated as per 262 * requirements stated in JLS {@jls 15.18.1} "String Concatenation Operator +". 263 * The inputs are converted as per JLS {@jls 5.1.11} "String Conversion", 264 * and combined from left to right.</li> 265 * </ul> 266 * 267 * <p>The concatenation <em>recipe</em> is a String description for the way to 268 * construct a concatenated String from the arguments and constants. The 269 * recipe is processed from left to right, and each character represents an 270 * input to concatenation. Recipe characters mean: 271 * 272 * <ul> 273 * 274 * <li><em>\1 (Unicode point 0001)</em>: an ordinary argument. This 275 * input is passed through dynamic argument, and is provided during the 276 * concatenation method invocation. This input can be null.</li> 277 * 278 * <li><em>\2 (Unicode point 0002):</em> a constant. This input passed 279 * through static bootstrap argument. This constant can be any value 280 * representable in constant pool. If necessary, the factory would call 281 * {@code toString} to perform a one-time String conversion.</li> 282 * 283 * <li><em>Any other char value:</em> a single character constant.</li> 284 * </ul> 285 * 286 * <p>Assume the linkage arguments are as follows: 287 * 288 * <ul> 289 * <li>{@code concatType}, describing the {@code CallSite} signature</li> 290 * <li>{@code recipe}, describing the String recipe</li> 291 * <li>{@code constants}, the vararg array of constants</li> 292 * </ul> 293 * 294 * <p>Then the following linkage invariants must hold: 295 * 296 * <ul> 297 * <li>The number of parameter slots in {@code concatType} is less than 298 * or equal to 200</li> 299 * 300 * <li>The parameter count in {@code concatType} is equal to number of \1 tags 301 * in {@code recipe}</li> 302 * 303 * <li>The return type in {@code concatType} is assignable 304 * from {@link java.lang.String}, and matches the return type of the 305 * returned {@link MethodHandle}</li> 306 * 307 * <li>The number of elements in {@code constants} is equal to number of \2 308 * tags in {@code recipe}</li> 309 * </ul> 310 * 311 * @param lookup Represents a lookup context with the accessibility 312 * privileges of the caller. Specifically, the lookup 313 * context must have 314 * {@linkplain MethodHandles.Lookup#hasFullPrivilegeAccess() 315 * full privilege access}. 316 * When used with {@code invokedynamic}, this is stacked 317 * automatically by the VM. 318 * @param name The name of the method to implement. This name is 319 * arbitrary, and has no meaning for this linkage method. 320 * When used with {@code invokedynamic}, this is provided 321 * by the {@code NameAndType} of the {@code InvokeDynamic} 322 * structure and is stacked automatically by the VM. 323 * @param concatType The expected signature of the {@code CallSite}. The 324 * parameter types represent the types of dynamic concatenation 325 * arguments; the return type is always assignable from {@link 326 * java.lang.String}. When used with {@code 327 * invokedynamic}, this is provided by the {@code 328 * NameAndType} of the {@code InvokeDynamic} structure and 329 * is stacked automatically by the VM. 330 * @param recipe Concatenation recipe, described above. 331 * @param constants A vararg parameter representing the constants passed to 332 * the linkage method. 333 * @return a CallSite whose target can be used to perform String 334 * concatenation, with dynamic concatenation arguments described by the given 335 * {@code concatType}. 336 * @throws StringConcatException If any of the linkage invariants described 337 * here are violated, or the lookup context 338 * does not have private access privileges. 339 * @throws NullPointerException If any of the incoming arguments is null, or 340 * any constant in {@code recipe} is null. 341 * This will never happen when a bootstrap method 342 * is called with invokedynamic. 343 * @apiNote Code generators have three distinct ways to process a constant 344 * string operand S in a string concatenation expression. First, S can be 345 * materialized as a reference (using ldc) and passed as an ordinary argument 346 * (recipe '\1'). Or, S can be stored in the constant pool and passed as a 347 * constant (recipe '\2') . Finally, if S contains neither of the recipe 348 * tag characters ('\1', '\2') then S can be interpolated into the recipe 349 * itself, causing its characters to be inserted into the result. 350 * 351 * @jls 5.1.11 String Conversion 352 * @jls 15.18.1 String Concatenation Operator + 353 */ 354 public static CallSite makeConcatWithConstants(MethodHandles.Lookup lookup, 355 String name, 356 MethodType concatType, 357 String recipe, 358 Object... constants) 359 throws StringConcatException 360 { 361 Objects.requireNonNull(lookup, "Lookup is null"); 362 Objects.requireNonNull(name, "Name is null"); 363 Objects.requireNonNull(recipe, "Recipe is null"); 364 Objects.requireNonNull(concatType, "Concat type is null"); 365 Objects.requireNonNull(constants, "Constants are null"); 366 367 for (Object o : constants) { 368 Objects.requireNonNull(o, "Cannot accept null constants"); 369 } 370 371 if ((lookup.lookupModes() & MethodHandles.Lookup.PRIVATE) == 0) { 372 throw new StringConcatException("Invalid caller: " + 373 lookup.lookupClass().getName()); 374 } 375 376 String[] constantStrings = parseRecipe(concatType, recipe, constants); 377 378 if (!concatType.returnType().isAssignableFrom(String.class)) { 379 throw new StringConcatException( 380 "The return type should be compatible with String, but it is " + 381 concatType.returnType()); 382 } 383 384 if (concatType.parameterSlotCount() > MAX_INDY_CONCAT_ARG_SLOTS) { 385 throw new StringConcatException("Too many concat argument slots: " + 386 concatType.parameterSlotCount() + 387 ", can only accept " + 388 MAX_INDY_CONCAT_ARG_SLOTS); 389 } 390 391 try { 392 MethodHandle mh = makeSimpleConcat(concatType, constantStrings); 393 if (mh == null && concatType.parameterCount() <= HIGH_ARITY_THRESHOLD) { 394 mh = generateMHInlineCopy(concatType, constantStrings); 395 } 396 397 if (mh == null) { 398 mh = InlineHiddenClassStrategy.generate(lookup, concatType, constantStrings); 399 } 400 mh = mh.viewAsType(concatType, true); 401 402 return new ConstantCallSite(mh); 403 } catch (Error e) { 404 // Pass through any error 405 throw e; 406 } catch (Throwable t) { 407 throw new StringConcatException("Generator failed", t); 408 } 409 } 410 411 private static String[] parseRecipe(MethodType concatType, 412 String recipe, 413 Object[] constants) 414 throws StringConcatException 415 { 416 417 Objects.requireNonNull(recipe, "Recipe is null"); 418 int paramCount = concatType.parameterCount(); 419 // Array containing interleaving String constants, starting with 420 // the first prefix and ending with the final prefix: 421 // 422 // consts[0] + arg0 + consts[1] + arg 1 + ... + consts[paramCount]. 423 // 424 // consts will be null if there's no constant to insert at a position. 425 // An empty String constant will be replaced by null. 426 String[] consts = new String[paramCount + 1]; 427 428 int cCount = 0; 429 int oCount = 0; 430 431 StringBuilder acc = new StringBuilder(); 432 433 for (int i = 0; i < recipe.length(); i++) { 434 char c = recipe.charAt(i); 435 436 if (c == TAG_CONST) { 437 if (cCount == constants.length) { 438 // Not enough constants 439 throw constantMismatch(constants, cCount); 440 } 441 // Accumulate constant args along with any constants encoded 442 // into the recipe 443 acc.append(constants[cCount++]); 444 } else if (c == TAG_ARG) { 445 // Check for overflow 446 if (oCount >= paramCount) { 447 throw argumentMismatch(concatType, oCount); 448 } 449 450 // Flush any accumulated characters into a constant 451 consts[oCount++] = acc.length() > 0 ? acc.toString() : ""; 452 acc.setLength(0); 453 } else { 454 // Not a special character, this is a constant embedded into 455 // the recipe itself. 456 acc.append(c); 457 } 458 } 459 if (oCount != concatType.parameterCount()) { 460 throw argumentMismatch(concatType, oCount); 461 } 462 if (cCount < constants.length) { 463 throw constantMismatch(constants, cCount); 464 } 465 466 // Flush the remaining characters as constant: 467 consts[oCount] = acc.length() > 0 ? acc.toString() : ""; 468 return consts; 469 } 470 471 private static StringConcatException argumentMismatch(MethodType concatType, 472 int oCount) { 473 return new StringConcatException( 474 "Mismatched number of concat arguments: recipe wants " + 475 oCount + 476 " arguments, but signature provides " + 477 concatType.parameterCount()); 478 } 479 480 private static StringConcatException constantMismatch(Object[] constants, 481 int cCount) { 482 return new StringConcatException( 483 "Mismatched number of concat constants: recipe wants " + 484 cCount + 485 " constants, but only " + 486 constants.length + 487 " are passed"); 488 } 489 490 private static MethodHandle makeSimpleConcat(MethodType mt, String[] constants) { 491 int paramCount = mt.parameterCount(); 492 String suffix = constants[paramCount]; 493 494 // Fast-path trivial concatenations 495 if (paramCount == 0) { 496 return MethodHandles.insertArguments(newStringifier(), 0, suffix == null ? "" : suffix); 497 } 498 if (paramCount == 1) { 499 String prefix = constants[0]; 500 // Empty constants will be 501 if (prefix.isEmpty()) { 502 if (suffix.isEmpty()) { 503 return unaryConcat(mt.parameterType(0)); 504 } else if (!mt.hasPrimitives()) { 505 return MethodHandles.insertArguments(simpleConcat(), 1, suffix); 506 } // else fall-through 507 } else if (suffix.isEmpty() && !mt.hasPrimitives()) { 508 // Non-primitive argument 509 return MethodHandles.insertArguments(simpleConcat(), 0, prefix); 510 } // fall-through if there's both a prefix and suffix 511 } else if (paramCount == 2 && !mt.hasPrimitives() && suffix.isEmpty() 512 && constants[0].isEmpty() && constants[1].isEmpty()) { 513 // Two reference arguments, no surrounding constants 514 return simpleConcat(); 515 } 516 517 return null; 518 } 519 520 /** 521 * <p>This strategy replicates what StringBuilders are doing: it builds the 522 * byte[] array on its own and passes that byte[] array to String 523 * constructor. This strategy requires access to some private APIs in JDK, 524 * most notably, the private String constructor that accepts byte[] arrays 525 * without copying. 526 */ 527 private static MethodHandle generateMHInlineCopy(MethodType mt, String[] constants) { 528 int paramCount = mt.parameterCount(); 529 String suffix = constants[paramCount]; 530 531 532 // else... fall-through to slow-path 533 534 // Create filters and obtain filtered parameter types. Filters would be used in the beginning 535 // to convert the incoming arguments into the arguments we can process (e.g. Objects -> Strings). 536 // The filtered argument type list is used all over in the combinators below. 537 538 Class<?>[] ptypes = mt.erase().parameterArray(); 539 MethodHandle[] objFilters = null; 540 MethodHandle[] floatFilters = null; 541 MethodHandle[] doubleFilters = null; 542 for (int i = 0; i < ptypes.length; i++) { 543 Class<?> cl = ptypes[i]; 544 // Use int as the logical type for subword integral types 545 // (byte and short). char and boolean require special 546 // handling so don't change the logical type of those 547 ptypes[i] = promoteToIntType(ptypes[i]); 548 // Object, float and double will be eagerly transformed 549 // into a (non-null) String as a first step after invocation. 550 // Set up to use String as the logical type for such arguments 551 // internally. 552 if (cl == Object.class) { 553 if (objFilters == null) { 554 objFilters = new MethodHandle[ptypes.length]; 555 } 556 objFilters[i] = objectStringifier(); 557 ptypes[i] = String.class; 558 } else if (cl == float.class) { 559 if (floatFilters == null) { 560 floatFilters = new MethodHandle[ptypes.length]; 561 } 562 floatFilters[i] = floatStringifier(); 563 ptypes[i] = String.class; 564 } else if (cl == double.class) { 565 if (doubleFilters == null) { 566 doubleFilters = new MethodHandle[ptypes.length]; 567 } 568 doubleFilters[i] = doubleStringifier(); 569 ptypes[i] = String.class; 570 } 571 } 572 573 // Start building the combinator tree. The tree "starts" with (<parameters>)String, and "finishes" 574 // with the (byte[], long)String shape to invoke newString in StringConcatHelper. The combinators are 575 // assembled bottom-up, which makes the code arguably hard to read. 576 577 // Drop all remaining parameter types, leave only helper arguments: 578 MethodHandle mh = MethodHandles.dropArgumentsTrusted(newString(), 2, ptypes); 579 580 // Calculate the initialLengthCoder value by looking at all constant values and summing up 581 // their lengths and adjusting the encoded coder bit if needed 582 long initialLengthCoder = INITIAL_CODER; 583 584 for (String constant : constants) { 585 if (constant != null) { 586 initialLengthCoder = JLA.stringConcatMix(initialLengthCoder, constant); 587 } 588 } 589 590 // Mix in prependers. This happens when (byte[], long) = (storage, indexCoder) is already 591 // known from the combinators below. We are assembling the string backwards, so the index coded 592 // into indexCoder is the *ending* index. 593 mh = filterInPrependers(mh, constants, ptypes); 594 595 // Fold in byte[] instantiation at argument 0 596 MethodHandle newArrayCombinator; 597 if (suffix == null || suffix.isEmpty()) { 598 suffix = ""; 599 } 600 // newArray variant that deals with prepending any trailing constant 601 // 602 // initialLengthCoder is adjusted to have the correct coder 603 // and length: The newArrayWithSuffix method expects only the coder of the 604 // suffix to be encoded into indexCoder 605 initialLengthCoder -= suffix.length(); 606 newArrayCombinator = newArrayWithSuffix(suffix); 607 608 mh = MethodHandles.foldArgumentsWithCombiner(mh, 0, newArrayCombinator, 609 1 // index 610 ); 611 612 // Start combining length and coder mixers. 613 // 614 // Length is easy: constant lengths can be computed on the spot, and all non-constant 615 // shapes have been either converted to Strings, or explicit methods for getting the 616 // string length out of primitives are provided. 617 // 618 // Coders are more interesting. Only Object, String and char arguments (and constants) 619 // can have non-Latin1 encoding. It is easier to blindly convert constants to String, 620 // and deduce the coder from there. Arguments would be either converted to Strings 621 // during the initial filtering, or handled by specializations in MIXERS. 622 // 623 // The method handle shape before all mixers are combined in is: 624 // (long, <args>)String = ("indexCoder", <args>) 625 // 626 // We will bind the initialLengthCoder value to the last mixer (the one that will be 627 // executed first), then fold that in. This leaves the shape after all mixers are 628 // combined in as: 629 // (<args>)String = (<args>) 630 631 mh = filterAndFoldInMixers(mh, initialLengthCoder, ptypes); 632 633 // The method handle shape here is (<args>). 634 635 // Apply filters, converting the arguments: 636 if (objFilters != null) { 637 mh = MethodHandles.filterArguments(mh, 0, objFilters); 638 } 639 if (floatFilters != null) { 640 mh = MethodHandles.filterArguments(mh, 0, floatFilters); 641 } 642 if (doubleFilters != null) { 643 mh = MethodHandles.filterArguments(mh, 0, doubleFilters); 644 } 645 646 return mh; 647 } 648 649 // We need one prepender per argument, but also need to fold in constants. We do so by greedily 650 // creating prependers that fold in surrounding constants into the argument prepender. This reduces 651 // the number of unique MH combinator tree shapes we'll create in an application. 652 // Additionally we do this in chunks to reduce the number of combinators bound to the root tree, 653 // which simplifies the shape and makes construction of similar trees use less unique LF classes 654 private static MethodHandle filterInPrependers(MethodHandle mh, String[] constants, Class<?>[] ptypes) { 655 int pos; 656 int[] argPositions = null; 657 MethodHandle prepend; 658 for (pos = 0; pos < ptypes.length - 3; pos += 4) { 659 prepend = prepender(pos, constants, ptypes, 4); 660 argPositions = filterPrependArgPositions(argPositions, pos, 4); 661 mh = MethodHandles.filterArgumentsWithCombiner(mh, 1, prepend, argPositions); 662 } 663 if (pos < ptypes.length) { 664 int count = ptypes.length - pos; 665 prepend = prepender(pos, constants, ptypes, count); 666 argPositions = filterPrependArgPositions(argPositions, pos, count); 667 mh = MethodHandles.filterArgumentsWithCombiner(mh, 1, prepend, argPositions); 668 } 669 return mh; 670 } 671 672 static int[] filterPrependArgPositions(int[] argPositions, int pos, int count) { 673 if (argPositions == null || argPositions.length != count + 2) { 674 argPositions = new int[count + 2]; 675 argPositions[0] = 1; // indexCoder 676 argPositions[1] = 0; // storage 677 } 678 int limit = count + 2; 679 for (int i = 2; i < limit; i++) { 680 argPositions[i] = i + pos; 681 } 682 return argPositions; 683 } 684 685 686 // We need one mixer per argument. 687 private static MethodHandle filterAndFoldInMixers(MethodHandle mh, long initialLengthCoder, Class<?>[] ptypes) { 688 int pos; 689 int[] argPositions = null; 690 for (pos = 0; pos < ptypes.length - 4; pos += 4) { 691 // Compute new "index" in-place pairwise using old value plus the appropriate arguments. 692 MethodHandle mix = mixer(ptypes[pos], ptypes[pos + 1], ptypes[pos + 2], ptypes[pos + 3]); 693 argPositions = filterMixerArgPositions(argPositions, pos, 4); 694 mh = MethodHandles.filterArgumentsWithCombiner(mh, 0, 695 mix, argPositions); 696 } 697 698 if (pos < ptypes.length) { 699 // Mix in the last 1 to 4 parameters, insert the initialLengthCoder into the final mixer and 700 // fold the result into the main combinator 701 mh = foldInLastMixers(mh, initialLengthCoder, pos, ptypes, ptypes.length - pos); 702 } else if (ptypes.length == 0) { 703 // No mixer (constants only concat), insert initialLengthCoder directly 704 mh = MethodHandles.insertArguments(mh, 0, initialLengthCoder); 705 } 706 return mh; 707 } 708 709 static int[] filterMixerArgPositions(int[] argPositions, int pos, int count) { 710 if (argPositions == null || argPositions.length != count + 2) { 711 argPositions = new int[count + 1]; 712 argPositions[0] = 0; // indexCoder 713 } 714 int limit = count + 1; 715 for (int i = 1; i < limit; i++) { 716 argPositions[i] = i + pos; 717 } 718 return argPositions; 719 } 720 721 private static MethodHandle foldInLastMixers(MethodHandle mh, long initialLengthCoder, int pos, Class<?>[] ptypes, int count) { 722 MethodHandle mix = switch (count) { 723 case 1 -> mixer(ptypes[pos]); 724 case 2 -> mixer(ptypes[pos], ptypes[pos + 1]); 725 case 3 -> mixer(ptypes[pos], ptypes[pos + 1], ptypes[pos + 2]); 726 case 4 -> mixer(ptypes[pos], ptypes[pos + 1], ptypes[pos + 2], ptypes[pos + 3]); 727 default -> throw new IllegalArgumentException("Unexpected count: " + count); 728 }; 729 mix = MethodHandles.insertArguments(mix,0, initialLengthCoder); 730 // apply selected arguments on the 1-4 arg mixer and fold in the result 731 return switch (count) { 732 case 1 -> MethodHandles.foldArgumentsWithCombiner(mh, 0, mix, 733 1 + pos); 734 case 2 -> MethodHandles.foldArgumentsWithCombiner(mh, 0, mix, 735 1 + pos, 2 + pos); 736 case 3 -> MethodHandles.foldArgumentsWithCombiner(mh, 0, mix, 737 1 + pos, 2 + pos, 3 + pos); 738 case 4 -> MethodHandles.foldArgumentsWithCombiner(mh, 0, mix, 739 1 + pos, 2 + pos, 3 + pos, 4 + pos); 740 default -> throw new IllegalArgumentException(); 741 }; 742 } 743 744 // Simple prependers, single argument. May be used directly or as a 745 // building block for complex prepender combinators. 746 private static MethodHandle prepender(String prefix, Class<?> cl) { 747 if (prefix == null || prefix.isEmpty()) { 748 return noPrefixPrepender(cl); 749 } else { 750 return MethodHandles.insertArguments( 751 prepender(cl), 3, prefix); 752 } 753 } 754 755 private static MethodHandle prepender(Class<?> cl) { 756 int idx = classIndex(cl); 757 MethodHandle prepend = PREPENDERS[idx]; 758 if (prepend == null) { 759 PREPENDERS[idx] = prepend = JLA.stringConcatHelper("prepend", 760 methodType(long.class, long.class, byte[].class, 761 Wrapper.asPrimitiveType(cl), String.class)).rebind(); 762 } 763 return prepend; 764 } 765 766 private static MethodHandle noPrefixPrepender(Class<?> cl) { 767 int idx = classIndex(cl); 768 MethodHandle prepend = NO_PREFIX_PREPENDERS[idx]; 769 if (prepend == null) { 770 NO_PREFIX_PREPENDERS[idx] = prepend = MethodHandles.insertArguments(prepender(cl), 3, ""); 771 } 772 return prepend; 773 } 774 775 private static final int INT_IDX = 0, 776 CHAR_IDX = 1, 777 LONG_IDX = 2, 778 BOOLEAN_IDX = 3, 779 STRING_IDX = 4, 780 TYPE_COUNT = 5; 781 private static int classIndex(Class<?> cl) { 782 if (cl == String.class) return STRING_IDX; 783 if (cl == int.class) return INT_IDX; 784 if (cl == boolean.class) return BOOLEAN_IDX; 785 if (cl == char.class) return CHAR_IDX; 786 if (cl == long.class) return LONG_IDX; 787 throw new IllegalArgumentException("Unexpected class: " + cl); 788 } 789 790 // Constant argument lists used by the prepender MH builders 791 private static final int[] PREPEND_FILTER_FIRST_ARGS = new int[] { 0, 1, 2 }; 792 private static final int[] PREPEND_FILTER_SECOND_ARGS = new int[] { 0, 1, 3 }; 793 private static final int[] PREPEND_FILTER_THIRD_ARGS = new int[] { 0, 1, 4 }; 794 private static final int[] PREPEND_FILTER_FIRST_PAIR_ARGS = new int[] { 0, 1, 2, 3 }; 795 private static final int[] PREPEND_FILTER_SECOND_PAIR_ARGS = new int[] { 0, 1, 4, 5 }; 796 797 // Base MH for complex prepender combinators. 798 private static @Stable MethodHandle PREPEND_BASE; 799 private static MethodHandle prependBase() { 800 MethodHandle base = PREPEND_BASE; 801 if (base == null) { 802 base = PREPEND_BASE = MethodHandles.dropArguments( 803 MethodHandles.identity(long.class), 1, byte[].class); 804 } 805 return base; 806 } 807 808 private static final @Stable MethodHandle[][] DOUBLE_PREPENDERS = new MethodHandle[TYPE_COUNT][TYPE_COUNT]; 809 810 private static MethodHandle prepender(String prefix, Class<?> cl, String prefix2, Class<?> cl2) { 811 int idx1 = classIndex(cl); 812 int idx2 = classIndex(cl2); 813 MethodHandle prepend = DOUBLE_PREPENDERS[idx1][idx2]; 814 if (prepend == null) { 815 prepend = DOUBLE_PREPENDERS[idx1][idx2] = 816 MethodHandles.dropArguments(prependBase(), 2, cl, cl2); 817 } 818 prepend = MethodHandles.filterArgumentsWithCombiner(prepend, 0, prepender(prefix, cl), 819 PREPEND_FILTER_FIRST_ARGS); 820 return MethodHandles.filterArgumentsWithCombiner(prepend, 0, prepender(prefix2, cl2), 821 PREPEND_FILTER_SECOND_ARGS); 822 } 823 824 private static MethodHandle prepender(int pos, String[] constants, Class<?>[] ptypes, int count) { 825 // build the simple cases directly 826 if (count == 1) { 827 return prepender(constants[pos], ptypes[pos]); 828 } 829 if (count == 2) { 830 return prepender(constants[pos], ptypes[pos], constants[pos + 1], ptypes[pos + 1]); 831 } 832 // build a tree from an unbound prepender, allowing us to bind the constants in a batch as a final step 833 MethodHandle prepend = prependBase(); 834 if (count == 3) { 835 prepend = MethodHandles.dropArguments(prepend, 2, 836 ptypes[pos], ptypes[pos + 1], ptypes[pos + 2]); 837 prepend = MethodHandles.filterArgumentsWithCombiner(prepend, 0, 838 prepender(constants[pos], ptypes[pos], constants[pos + 1], ptypes[pos + 1]), 839 PREPEND_FILTER_FIRST_PAIR_ARGS); 840 return MethodHandles.filterArgumentsWithCombiner(prepend, 0, 841 prepender(constants[pos + 2], ptypes[pos + 2]), 842 PREPEND_FILTER_THIRD_ARGS); 843 } else if (count == 4) { 844 prepend = MethodHandles.dropArguments(prepend, 2, 845 ptypes[pos], ptypes[pos + 1], ptypes[pos + 2], ptypes[pos + 3]); 846 prepend = MethodHandles.filterArgumentsWithCombiner(prepend, 0, 847 prepender(constants[pos], ptypes[pos], constants[pos + 1], ptypes[pos + 1]), 848 PREPEND_FILTER_FIRST_PAIR_ARGS); 849 return MethodHandles.filterArgumentsWithCombiner(prepend, 0, 850 prepender(constants[pos + 2], ptypes[pos + 2], constants[pos + 3], ptypes[pos + 3]), 851 PREPEND_FILTER_SECOND_PAIR_ARGS); 852 } else { 853 throw new IllegalArgumentException("Unexpected count: " + count); 854 } 855 } 856 857 // Constant argument lists used by the mixer MH builders 858 private static final int[] MIX_FILTER_SECOND_ARGS = new int[] { 0, 2 }; 859 private static final int[] MIX_FILTER_THIRD_ARGS = new int[] { 0, 3 }; 860 private static final int[] MIX_FILTER_SECOND_PAIR_ARGS = new int[] { 0, 3, 4 }; 861 private static MethodHandle mixer(Class<?> cl) { 862 int index = classIndex(cl); 863 MethodHandle mix = MIXERS[index]; 864 if (mix == null) { 865 MIXERS[index] = mix = JLA.stringConcatHelper("mix", 866 methodType(long.class, long.class, Wrapper.asPrimitiveType(cl))).rebind(); 867 } 868 return mix; 869 } 870 871 private static final @Stable MethodHandle[][] DOUBLE_MIXERS = new MethodHandle[TYPE_COUNT][TYPE_COUNT]; 872 private static MethodHandle mixer(Class<?> cl, Class<?> cl2) { 873 int idx1 = classIndex(cl); 874 int idx2 = classIndex(cl2); 875 MethodHandle mix = DOUBLE_MIXERS[idx1][idx2]; 876 if (mix == null) { 877 mix = mixer(cl); 878 mix = MethodHandles.dropArguments(mix, 2, cl2); 879 DOUBLE_MIXERS[idx1][idx2] = mix = MethodHandles.filterArgumentsWithCombiner(mix, 0, 880 mixer(cl2), MIX_FILTER_SECOND_ARGS); 881 } 882 return mix; 883 } 884 885 private static MethodHandle mixer(Class<?> cl, Class<?> cl2, Class<?> cl3) { 886 MethodHandle mix = mixer(cl, cl2); 887 mix = MethodHandles.dropArguments(mix, 3, cl3); 888 return MethodHandles.filterArgumentsWithCombiner(mix, 0, 889 mixer(cl3), MIX_FILTER_THIRD_ARGS); 890 } 891 892 private static MethodHandle mixer(Class<?> cl, Class<?> cl2, Class<?> cl3, Class<?> cl4) { 893 MethodHandle mix = mixer(cl, cl2); 894 mix = MethodHandles.dropArguments(mix, 3, cl3, cl4); 895 return MethodHandles.filterArgumentsWithCombiner(mix, 0, 896 mixer(cl3, cl4), MIX_FILTER_SECOND_PAIR_ARGS); 897 } 898 899 private @Stable static MethodHandle SIMPLE_CONCAT; 900 private static MethodHandle simpleConcat() { 901 MethodHandle mh = SIMPLE_CONCAT; 902 if (mh == null) { 903 MethodHandle simpleConcat = JLA.stringConcatHelper("simpleConcat", 904 methodType(String.class, Object.class, Object.class)); 905 SIMPLE_CONCAT = mh = simpleConcat.rebind(); 906 } 907 return mh; 908 } 909 910 private @Stable static MethodHandle NEW_STRING; 911 private static MethodHandle newString() { 912 MethodHandle mh = NEW_STRING; 913 if (mh == null) { 914 MethodHandle newString = JLA.stringConcatHelper("newString", 915 methodType(String.class, byte[].class, long.class)); 916 NEW_STRING = mh = newString.rebind(); 917 } 918 return mh; 919 } 920 921 private @Stable static MethodHandle NEW_ARRAY_SUFFIX; 922 private static MethodHandle newArrayWithSuffix(String suffix) { 923 MethodHandle mh = NEW_ARRAY_SUFFIX; 924 if (mh == null) { 925 MethodHandle newArrayWithSuffix = JLA.stringConcatHelper("newArrayWithSuffix", 926 methodType(byte[].class, String.class, long.class)); 927 NEW_ARRAY_SUFFIX = mh = newArrayWithSuffix.rebind(); 928 } 929 return MethodHandles.insertArguments(mh, 0, suffix); 930 } 931 932 /** 933 * Public gateways to public "stringify" methods. These methods have the 934 * form String apply(T obj), and normally delegate to {@code String.valueOf}, 935 * depending on argument's type. 936 */ 937 private @Stable static MethodHandle OBJECT_STRINGIFIER; 938 private static MethodHandle objectStringifier() { 939 MethodHandle mh = OBJECT_STRINGIFIER; 940 if (mh == null) { 941 OBJECT_STRINGIFIER = mh = JLA.stringConcatHelper("stringOf", 942 methodType(String.class, Object.class)); 943 } 944 return mh; 945 } 946 private @Stable static MethodHandle FLOAT_STRINGIFIER; 947 private static MethodHandle floatStringifier() { 948 MethodHandle mh = FLOAT_STRINGIFIER; 949 if (mh == null) { 950 FLOAT_STRINGIFIER = mh = stringValueOf(float.class); 951 } 952 return mh; 953 } 954 private @Stable static MethodHandle DOUBLE_STRINGIFIER; 955 private static MethodHandle doubleStringifier() { 956 MethodHandle mh = DOUBLE_STRINGIFIER; 957 if (mh == null) { 958 DOUBLE_STRINGIFIER = mh = stringValueOf(double.class); 959 } 960 return mh; 961 } 962 963 private @Stable static MethodHandle INT_STRINGIFIER; 964 private static MethodHandle intStringifier() { 965 MethodHandle mh = INT_STRINGIFIER; 966 if (mh == null) { 967 INT_STRINGIFIER = mh = stringValueOf(int.class); 968 } 969 return mh; 970 } 971 972 private @Stable static MethodHandle LONG_STRINGIFIER; 973 private static MethodHandle longStringifier() { 974 MethodHandle mh = LONG_STRINGIFIER; 975 if (mh == null) { 976 LONG_STRINGIFIER = mh = stringValueOf(long.class); 977 } 978 return mh; 979 } 980 981 private @Stable static MethodHandle CHAR_STRINGIFIER; 982 private static MethodHandle charStringifier() { 983 MethodHandle mh = CHAR_STRINGIFIER; 984 if (mh == null) { 985 CHAR_STRINGIFIER = mh = stringValueOf(char.class); 986 } 987 return mh; 988 } 989 990 private @Stable static MethodHandle BOOLEAN_STRINGIFIER; 991 private static MethodHandle booleanStringifier() { 992 MethodHandle mh = BOOLEAN_STRINGIFIER; 993 if (mh == null) { 994 BOOLEAN_STRINGIFIER = mh = stringValueOf(boolean.class); 995 } 996 return mh; 997 } 998 999 private @Stable static MethodHandle NEW_STRINGIFIER; 1000 private static MethodHandle newStringifier() { 1001 MethodHandle mh = NEW_STRINGIFIER; 1002 if (mh == null) { 1003 NEW_STRINGIFIER = mh = JLA.stringConcatHelper("newStringOf", 1004 methodType(String.class, Object.class)); 1005 } 1006 return mh; 1007 } 1008 1009 private static MethodHandle unaryConcat(Class<?> cl) { 1010 if (!cl.isPrimitive()) { 1011 return newStringifier(); 1012 } else if (cl == int.class || cl == short.class || cl == byte.class) { 1013 return intStringifier(); 1014 } else if (cl == long.class) { 1015 return longStringifier(); 1016 } else if (cl == char.class) { 1017 return charStringifier(); 1018 } else if (cl == boolean.class) { 1019 return booleanStringifier(); 1020 } else if (cl == float.class) { 1021 return floatStringifier(); 1022 } else if (cl == double.class) { 1023 return doubleStringifier(); 1024 } else { 1025 throw new InternalError("Unhandled type for unary concatenation: " + cl); 1026 } 1027 } 1028 1029 private static final @Stable MethodHandle[] NO_PREFIX_PREPENDERS = new MethodHandle[TYPE_COUNT]; 1030 private static final @Stable MethodHandle[] PREPENDERS = new MethodHandle[TYPE_COUNT]; 1031 private static final @Stable MethodHandle[] MIXERS = new MethodHandle[TYPE_COUNT]; 1032 private static final long INITIAL_CODER = JLA.stringConcatInitialCoder(); 1033 1034 /** 1035 * Promote integral types to int. 1036 */ 1037 private static Class<?> promoteToIntType(Class<?> t) { 1038 // use int for subword integral types; still need special mixers 1039 // and prependers for char, boolean 1040 return t == byte.class || t == short.class ? int.class : t; 1041 } 1042 1043 /** 1044 * Returns a stringifier for references and floats/doubles only. 1045 * Always returns null for other primitives. 1046 * 1047 * @param t class to stringify 1048 * @return stringifier; null, if not available 1049 */ 1050 private static MethodHandle stringifierFor(Class<?> t) { 1051 if (t == Object.class) { 1052 return objectStringifier(); 1053 } else if (t == float.class) { 1054 return floatStringifier(); 1055 } else if (t == double.class) { 1056 return doubleStringifier(); 1057 } 1058 return null; 1059 } 1060 1061 private static MethodHandle stringValueOf(Class<?> ptype) { 1062 try { 1063 return MethodHandles.publicLookup() 1064 .findStatic(String.class, "valueOf", MethodType.methodType(String.class, ptype)); 1065 } catch (NoSuchMethodException | IllegalAccessException e) { 1066 throw new AssertionError(e); 1067 } 1068 } 1069 1070 private StringConcatFactory() { 1071 // no instantiation 1072 } 1073 1074 /** 1075 * Implement efficient hidden class strategy for String concatenation 1076 * 1077 * <p>This strategy replicates based on the bytecode what StringBuilders are doing: it builds the 1078 * byte[] array on its own and passes that byte[] array to String 1079 * constructor. This strategy requires access to some private APIs in JDK, 1080 * most notably, the private String constructor that accepts byte[] arrays 1081 * without copying. 1082 */ 1083 private static final class InlineHiddenClassStrategy { 1084 static final String CLASS_NAME = "java.lang.String$$StringConcat"; 1085 static final String METHOD_NAME = "concat"; 1086 1087 static final ClassFileDumper DUMPER = 1088 ClassFileDumper.getInstance("java.lang.invoke.StringConcatFactory.dump", "stringConcatClasses"); 1089 static final MethodHandles.Lookup STR_LOOKUP = new MethodHandles.Lookup(String.class); 1090 1091 static final ClassDesc CD_CONCAT = ConstantUtils.binaryNameToDesc(CLASS_NAME); 1092 static final ClassDesc CD_StringConcatHelper = ReferenceClassDescImpl.ofValidated("Ljava/lang/StringConcatHelper;"); 1093 static final ClassDesc CD_StringConcatBase = ReferenceClassDescImpl.ofValidated("Ljava/lang/StringConcatHelper$StringConcatBase;"); 1094 static final ClassDesc CD_Array_byte = ReferenceClassDescImpl.ofValidated("[B"); 1095 static final ClassDesc CD_Array_String = ReferenceClassDescImpl.ofValidated("[Ljava/lang/String;"); 1096 1097 static final MethodTypeDesc MTD_byte_char = MethodTypeDescImpl.ofValidated(CD_byte, CD_char); 1098 static final MethodTypeDesc MTD_byte = MethodTypeDescImpl.ofValidated(CD_byte); 1099 static final MethodTypeDesc MTD_int = MethodTypeDescImpl.ofValidated(CD_int); 1100 static final MethodTypeDesc MTD_int_int_boolean = MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_boolean); 1101 static final MethodTypeDesc MTD_int_int_char = MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_char); 1102 static final MethodTypeDesc MTD_int_int_int = MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_int); 1103 static final MethodTypeDesc MTD_int_int_long = MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_long); 1104 static final MethodTypeDesc MTD_int_int_String = MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_String); 1105 static final MethodTypeDesc MTD_String_float = MethodTypeDescImpl.ofValidated(CD_String, CD_float); 1106 static final MethodTypeDesc MTD_String_double = MethodTypeDescImpl.ofValidated(CD_String, CD_double); 1107 static final MethodTypeDesc MTD_String_Object = MethodTypeDescImpl.ofValidated(CD_String, CD_Object); 1108 1109 static final MethodTypeDesc MTD_INIT = MethodTypeDescImpl.ofValidated(CD_void, CD_Array_String); 1110 static final MethodTypeDesc MTD_NEW_ARRAY_SUFFIX = MethodTypeDescImpl.ofValidated(CD_Array_byte, CD_String, CD_int, CD_byte); 1111 static final MethodTypeDesc MTD_STRING_INIT = MethodTypeDescImpl.ofValidated(CD_void, CD_Array_byte, CD_byte); 1112 1113 static final MethodTypeDesc PREPEND_int = MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_byte, CD_Array_byte, CD_int, CD_String); 1114 static final MethodTypeDesc PREPEND_long = MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_byte, CD_Array_byte, CD_long, CD_String); 1115 static final MethodTypeDesc PREPEND_boolean = MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_byte, CD_Array_byte, CD_boolean, CD_String); 1116 static final MethodTypeDesc PREPEND_char = MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_byte, CD_Array_byte, CD_char, CD_String); 1117 static final MethodTypeDesc PREPEND_String = MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_byte, CD_Array_byte, CD_String, CD_String); 1118 1119 static final RuntimeVisibleAnnotationsAttribute FORCE_INLINE = RuntimeVisibleAnnotationsAttribute.of(Annotation.of(ClassDesc.ofDescriptor("Ljdk/internal/vm/annotation/ForceInline;"))); 1120 1121 static final MethodType CONSTRUCTOR_METHOD_TYPE = MethodType.methodType(void.class, String[].class); 1122 static final Consumer<CodeBuilder> CONSTRUCTOR_BUILDER = new Consumer<CodeBuilder>() { 1123 @Override 1124 public void accept(CodeBuilder cb) { 1125 /* 1126 * super(constants); 1127 */ 1128 int thisSlot = cb.receiverSlot(), 1129 constantsSlot = cb.parameterSlot(0); 1130 cb.aload(thisSlot) 1131 .aload(constantsSlot) 1132 .invokespecial(CD_StringConcatBase, INIT_NAME, MTD_INIT, false) 1133 .return_(); 1134 } 1135 }; 1136 1137 static final ReferencedKeyMap<MethodType, SoftReference<MethodHandlePair>> CACHE = 1138 ReferencedKeyMap.create(true, true, 1139 new Supplier<>() { 1140 @Override 1141 public Map<ReferenceKey<MethodType>, SoftReference<MethodHandlePair>> get() { 1142 return new ConcurrentHashMap<>(64); 1143 } 1144 }); 1145 1146 private InlineHiddenClassStrategy() { 1147 // no instantiation 1148 } 1149 1150 private record MethodHandlePair(MethodHandle constructor, MethodHandle concatenator) { }; 1151 1152 /** 1153 * The parameter types are normalized into 7 types: int,long,boolean,char,float,double,Object 1154 */ 1155 private static MethodType erasedArgs(MethodType args) { 1156 int parameterCount = args.parameterCount(); 1157 var paramTypes = new Class<?>[parameterCount]; 1158 boolean changed = false; 1159 for (int i = 0; i < parameterCount; i++) { 1160 Class<?> cl = args.parameterType(i); 1161 // Use int as the logical type for subword integral types 1162 // (byte and short). char and boolean require special 1163 // handling so don't change the logical type of those 1164 if (cl == byte.class || cl == short.class) { 1165 cl = int.class; 1166 changed = true; 1167 } else if (cl != Object.class && !cl.isPrimitive()) { 1168 cl = Object.class; 1169 changed = true; 1170 } 1171 paramTypes[i] = cl; 1172 } 1173 return changed ? MethodType.methodType(args.returnType(), paramTypes, true) : args; 1174 } 1175 1176 /** 1177 * Construct the MethodType of the prepend method, The parameters only support 5 types: 1178 * int/long/char/boolean/String. Not int/long/char/boolean type, use String type<p> 1179 * 1180 * The following is an example of the generated target code: 1181 * <blockquote><pre> 1182 * int prepend(int length, byte coder, byte[] buff, String[] constants 1183 * int arg0, long arg1, boolean arg2, char arg3, String arg5) 1184 * </pre></blockquote> 1185 */ 1186 private static MethodTypeDesc prependArgs(MethodType concatArgs, boolean staticConcat) { 1187 int parameterCount = concatArgs.parameterCount(); 1188 int prefixArgs = staticConcat ? 3 : 4; 1189 var paramTypes = new ClassDesc[parameterCount + prefixArgs]; 1190 paramTypes[0] = CD_int; // length 1191 paramTypes[1] = CD_byte; // coder 1192 paramTypes[2] = CD_Array_byte; // buff 1193 1194 if (!staticConcat) { 1195 paramTypes[3] = CD_Array_String; // constants 1196 } 1197 1198 for (int i = 0; i < parameterCount; i++) { 1199 var cl = concatArgs.parameterType(i); 1200 paramTypes[i + prefixArgs] = needStringOf(cl) ? CD_String : ConstantUtils.classDesc(cl); 1201 } 1202 return MethodTypeDescImpl.ofValidated(CD_int, paramTypes); 1203 } 1204 1205 /** 1206 * Construct the MethodType of the coder method. The first parameter is the initialized coder. 1207 * Only parameter types which can be UTF16 are added. 1208 * Returns null if no such parameter exists or CompactStrings is off. 1209 */ 1210 private static MethodTypeDesc coderArgsIfMaybeUTF16(MethodType concatArgs) { 1211 if (JLA.stringInitCoder() != 0) { 1212 return null; 1213 } 1214 1215 int parameterCount = concatArgs.parameterCount(); 1216 1217 int maybeUTF16Count = 0; 1218 for (int i = 0; i < parameterCount; i++) { 1219 if (maybeUTF16(concatArgs.parameterType(i))) { 1220 maybeUTF16Count++; 1221 } 1222 } 1223 1224 if (maybeUTF16Count == 0) { 1225 return null; 1226 } 1227 1228 var paramTypes = new ClassDesc[maybeUTF16Count + 1]; 1229 paramTypes[0] = CD_int; // init coder 1230 for (int i = 0, paramIndex = 1; i < parameterCount; i++) { 1231 var cl = concatArgs.parameterType(i); 1232 if (maybeUTF16(cl)) { 1233 paramTypes[paramIndex++] = cl == char.class ? CD_char : CD_String; 1234 } 1235 } 1236 return MethodTypeDescImpl.ofValidated(CD_int, paramTypes); 1237 } 1238 1239 /** 1240 * Construct the MethodType of the length method, 1241 * The first parameter is the initialized length 1242 */ 1243 private static MethodTypeDesc lengthArgs(MethodType concatArgs) { 1244 int parameterCount = concatArgs.parameterCount(); 1245 var paramTypes = new ClassDesc[parameterCount + 1]; 1246 paramTypes[0] = CD_int; // init long 1247 for (int i = 0; i < parameterCount; i++) { 1248 var cl = concatArgs.parameterType(i); 1249 paramTypes[i + 1] = needStringOf(cl) ? CD_String : ConstantUtils.classDesc(cl); 1250 } 1251 return MethodTypeDescImpl.ofValidated(CD_int, paramTypes); 1252 } 1253 1254 private static MethodHandle generate(Lookup lookup, MethodType args, String[] constants) throws Exception { 1255 lookup = STR_LOOKUP; 1256 final MethodType concatArgs = erasedArgs(args); 1257 1258 // 1 argument use built-in method 1259 if (args.parameterCount() == 1) { 1260 Object concat1 = JLA.stringConcat1(constants); 1261 var handle = lookup.findVirtual(concat1.getClass(), METHOD_NAME, concatArgs); 1262 return handle.bindTo(concat1); 1263 } 1264 1265 boolean forceInline = concatArgs.parameterCount() < FORCE_INLINE_THRESHOLD; 1266 boolean staticConcat = concatArgs.parameterCount() >= CACHE_THRESHOLD; 1267 1268 if (!staticConcat) { 1269 var weakConstructorHandle = CACHE.get(concatArgs); 1270 if (weakConstructorHandle != null) { 1271 MethodHandlePair handlePair = weakConstructorHandle.get(); 1272 if (handlePair != null) { 1273 try { 1274 var instance = handlePair.constructor.invokeBasic((Object)constants); 1275 return handlePair.concatenator.bindTo(instance); 1276 } catch (Throwable e) { 1277 throw new StringConcatException("Exception while utilizing the hidden class", e); 1278 } 1279 } 1280 } 1281 } 1282 1283 MethodTypeDesc lengthArgs = lengthArgs(concatArgs), 1284 coderArgs = coderArgsIfMaybeUTF16(concatArgs), 1285 prependArgs = prependArgs(concatArgs, staticConcat); 1286 1287 byte[] classBytes = ClassFile.of().build(CD_CONCAT, 1288 new Consumer<ClassBuilder>() { 1289 @Override 1290 public void accept(ClassBuilder clb) { 1291 if (staticConcat) { 1292 clb.withSuperclass(CD_Object) 1293 .withFlags(ACC_ABSTRACT | ACC_SUPER | ACC_SYNTHETIC); 1294 } else { 1295 clb.withSuperclass(CD_StringConcatBase) 1296 .withFlags(ACC_FINAL | ACC_SUPER | ACC_SYNTHETIC) 1297 .withMethodBody(INIT_NAME, MTD_INIT, 0, CONSTRUCTOR_BUILDER); 1298 } 1299 1300 clb.withMethod("length", 1301 lengthArgs, 1302 ACC_STATIC | ACC_PRIVATE, 1303 new Consumer<MethodBuilder>() { 1304 public void accept(MethodBuilder mb) { 1305 if (forceInline) { 1306 mb.with(FORCE_INLINE); 1307 } 1308 mb.withCode(generateLengthMethod(lengthArgs)); 1309 } 1310 }) 1311 .withMethod("prepend", 1312 prependArgs, 1313 ACC_STATIC | ACC_PRIVATE, 1314 new Consumer<MethodBuilder>() { 1315 public void accept(MethodBuilder mb) { 1316 if (forceInline) { 1317 mb.with(FORCE_INLINE); 1318 } 1319 mb.withCode(generatePrependMethod(prependArgs, staticConcat, constants)); 1320 } 1321 }) 1322 .withMethod(METHOD_NAME, 1323 ConstantUtils.methodTypeDesc(concatArgs), 1324 staticConcat ? ACC_STATIC | ACC_FINAL : ACC_FINAL, 1325 new Consumer<MethodBuilder>() { 1326 public void accept(MethodBuilder mb) { 1327 if (forceInline) { 1328 mb.with(FORCE_INLINE); 1329 } 1330 mb.withCode(generateConcatMethod( 1331 staticConcat, 1332 constants, 1333 CD_CONCAT, 1334 concatArgs, 1335 lengthArgs, 1336 coderArgs, 1337 prependArgs)); 1338 } 1339 }); 1340 1341 if (coderArgs != null) { 1342 clb.withMethod("coder", 1343 coderArgs, 1344 ACC_STATIC | ACC_PRIVATE, 1345 new Consumer<MethodBuilder>() { 1346 public void accept(MethodBuilder mb) { 1347 if (forceInline) { 1348 mb.with(FORCE_INLINE); 1349 } 1350 mb.withCode(generateCoderMethod(coderArgs)); 1351 } 1352 }); 1353 } 1354 }}); 1355 try { 1356 var hiddenClass = lookup.makeHiddenClassDefiner(CLASS_NAME, classBytes, Set.of(), DUMPER) 1357 .defineClass(true, null); 1358 1359 if (staticConcat) { 1360 return lookup.findStatic(hiddenClass, METHOD_NAME, concatArgs); 1361 } 1362 1363 var constructor = lookup.findConstructor(hiddenClass, CONSTRUCTOR_METHOD_TYPE); 1364 var concatenator = lookup.findVirtual(hiddenClass, METHOD_NAME, concatArgs); 1365 CACHE.put(concatArgs, new SoftReference<>(new MethodHandlePair(constructor, concatenator))); 1366 var instance = constructor.invokeBasic((Object)constants); 1367 return concatenator.bindTo(instance); 1368 } catch (Throwable e) { 1369 throw new StringConcatException("Exception while spinning the class", e); 1370 } 1371 } 1372 1373 /** 1374 * Generate InlineCopy-based code. <p> 1375 * 1376 * The following is an example of the generated target code: 1377 * 1378 * <blockquote><pre> 1379 * import static java.lang.StringConcatHelper.newArrayWithSuffix; 1380 * import static java.lang.StringConcatHelper.prepend; 1381 * import static java.lang.StringConcatHelper.stringCoder; 1382 * import static java.lang.StringConcatHelper.stringSize; 1383 * 1384 * class StringConcat extends java.lang.StringConcatHelper.StringConcatBase { 1385 * // super class defines 1386 * // String[] constants; 1387 * // int length; 1388 * // byte coder; 1389 * 1390 * StringConcat(String[] constants) { 1391 * super(constants); 1392 * } 1393 * 1394 * String concat(int arg0, long arg1, boolean arg2, char arg3, String arg4, 1395 * float arg5, double arg6, Object arg7 1396 * ) { 1397 * // Types other than byte/short/int/long/boolean/String require a local variable to store 1398 * String str4 = stringOf(arg4); 1399 * String str5 = stringOf(arg5); 1400 * String str6 = stringOf(arg6); 1401 * String str7 = stringOf(arg7); 1402 * 1403 * int coder = coder(this.coder, arg0, arg1, arg2, arg3, str4, str5, str6, str7); 1404 * int length = length(this.length, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); 1405 * String[] constants = this.constants; 1406 * byte[] buf = newArrayWithSuffix(constants[paramCount], length. coder); 1407 * 1408 * prepend(length, coder, buf, constants, arg0, arg1, arg2, arg3, str4, str5, str6, str7); 1409 * 1410 * return new String(buf, coder); 1411 * } 1412 * 1413 * static int length(int length, int arg0, long arg1, boolean arg2, char arg3, 1414 * String arg4, String arg5, String arg6, String arg7) { 1415 * return stringSize(stringSize(stringSize(stringSize(stringSize(stringSize(stringSize(stringSize( 1416 * length, arg0), arg1), arg2), arg3), arg4), arg5), arg6), arg7); 1417 * } 1418 * 1419 * static int cocder(int coder, char arg3, String str4, String str5, String str6, String str7) { 1420 * return coder | stringCoder(arg3) | str4.coder() | str5.coder() | str6.coder() | str7.coder(); 1421 * } 1422 * 1423 * static int prepend(int length, int coder, byte[] buf, String[] constants, 1424 * int arg0, long arg1, boolean arg2, char arg3, 1425 * String str4, String str5, String str6, String str7) { 1426 * // StringConcatHelper.prepend 1427 * return prepend(prepend(prepend(prepend( 1428 * prepend(apppend(prepend(prepend(length, 1429 * buf, str7, constant[7]), buf, str6, constant[6]), 1430 * buf, str5, constant[5]), buf, str4, constant[4]), 1431 * buf, arg3, constant[3]), buf, arg2, constant[2]), 1432 * buf, arg1, constant[1]), buf, arg0, constant[0]); 1433 * } 1434 * } 1435 * </pre></blockquote> 1436 */ 1437 private static Consumer<CodeBuilder> generateConcatMethod( 1438 boolean staticConcat, 1439 String[] constants, 1440 ClassDesc concatClass, 1441 MethodType concatArgs, 1442 MethodTypeDesc lengthArgs, 1443 MethodTypeDesc coderArgs, 1444 MethodTypeDesc prependArgs 1445 ) { 1446 return new Consumer<CodeBuilder>() { 1447 @Override 1448 public void accept(CodeBuilder cb) { 1449 // Compute parameter variable slots 1450 int paramCount = concatArgs.parameterCount(), 1451 thisSlot = staticConcat ? 0 : cb.receiverSlot(), 1452 lengthSlot = cb.allocateLocal(TypeKind.INT), 1453 coderSlot = cb.allocateLocal(TypeKind.BYTE), 1454 bufSlot = cb.allocateLocal(TypeKind.REFERENCE), 1455 constantsSlot = cb.allocateLocal(TypeKind.REFERENCE), 1456 suffixSlot = cb.allocateLocal(TypeKind.REFERENCE); 1457 1458 /* 1459 * Types other than int/long/char/boolean require local variables to store the result of stringOf. 1460 * 1461 * stringSlots stores the slots of parameters relative to local variables 1462 * 1463 * str0 = stringOf(arg0); 1464 * str1 = stringOf(arg1); 1465 * ... 1466 * strN = toString(argN); 1467 */ 1468 int[] stringSlots = new int[paramCount]; 1469 for (int i = 0; i < paramCount; i++) { 1470 var cl = concatArgs.parameterType(i); 1471 if (needStringOf(cl)) { 1472 MethodTypeDesc methodTypeDesc; 1473 if (cl == float.class) { 1474 methodTypeDesc = MTD_String_float; 1475 } else if (cl == double.class) { 1476 methodTypeDesc = MTD_String_double; 1477 } else { 1478 methodTypeDesc = MTD_String_Object; 1479 } 1480 stringSlots[i] = cb.allocateLocal(TypeKind.REFERENCE); 1481 cb.loadLocal(TypeKind.from(cl), cb.parameterSlot(i)) 1482 .invokestatic(CD_StringConcatHelper, "stringOf", methodTypeDesc) 1483 .astore(stringSlots[i]); 1484 } 1485 } 1486 1487 int coder = JLA.stringInitCoder(), 1488 length = 0; 1489 if (staticConcat) { 1490 for (var constant : constants) { 1491 coder |= JLA.stringCoder(constant); 1492 length += constant.length(); 1493 } 1494 } 1495 1496 /* 1497 * coder = coder(this.coder, arg0, arg1, ... argN); 1498 */ 1499 if (staticConcat) { 1500 // coder can only be 0 or 1 1501 if (coder == 0) { 1502 cb.iconst_0(); 1503 } else { 1504 cb.iconst_1(); 1505 } 1506 } else { 1507 cb.aload(thisSlot) 1508 .getfield(concatClass, "coder", CD_byte); 1509 } 1510 1511 if (coderArgs != null) { 1512 for (int i = 0; i < paramCount; i++) { 1513 var cl = concatArgs.parameterType(i); 1514 if (maybeUTF16(cl)) { 1515 if (cl == char.class) { 1516 cb.loadLocal(TypeKind.CHAR, cb.parameterSlot(i)); 1517 } else { 1518 cb.aload(stringSlots[i]); 1519 } 1520 } 1521 } 1522 cb.invokestatic(concatClass, "coder", coderArgs); 1523 } 1524 cb.istore(coderSlot); 1525 1526 /* 1527 * length = length(this.length, arg0, arg1, ..., argN); 1528 */ 1529 if (staticConcat) { 1530 cb.ldc(length); 1531 } else { 1532 cb.aload(thisSlot) 1533 .getfield(concatClass, "length", CD_int); 1534 } 1535 1536 for (int i = 0; i < paramCount; i++) { 1537 var cl = concatArgs.parameterType(i); 1538 int paramSlot = cb.parameterSlot(i); 1539 if (needStringOf(cl)) { 1540 paramSlot = stringSlots[i]; 1541 cl = String.class; 1542 } 1543 cb.loadLocal(TypeKind.from(cl), paramSlot); 1544 } 1545 cb.invokestatic(concatClass, "length", lengthArgs); 1546 1547 /* 1548 * String[] constants = this.constants; 1549 * suffix = constants[paramCount]; 1550 * length -= suffix.length(); 1551 */ 1552 if (staticConcat) { 1553 cb.ldc(constants[paramCount].length()) 1554 .isub() 1555 .istore(lengthSlot); 1556 } else { 1557 cb.aload(thisSlot) 1558 .getfield(concatClass, "constants", CD_Array_String) 1559 .dup() 1560 .astore(constantsSlot) 1561 .ldc(paramCount) 1562 .aaload() 1563 .dup() 1564 .astore(suffixSlot) 1565 .invokevirtual(CD_String, "length", MTD_int) 1566 .isub() 1567 .istore(lengthSlot); 1568 } 1569 1570 /* 1571 * Allocate buffer : 1572 * 1573 * buf = newArrayWithSuffix(suffix, length, coder) 1574 */ 1575 if (staticConcat) { 1576 cb.ldc(constants[paramCount]); 1577 } else { 1578 cb.aload(suffixSlot); 1579 } 1580 cb.iload(lengthSlot) 1581 .iload(coderSlot) 1582 .invokestatic(CD_StringConcatHelper, "newArrayWithSuffix", MTD_NEW_ARRAY_SUFFIX) 1583 .astore(bufSlot); 1584 1585 /* 1586 * prepend(length, coder, buf, constants, ar0, ar1, ..., argN); 1587 */ 1588 cb.iload(lengthSlot) 1589 .iload(coderSlot) 1590 .aload(bufSlot); 1591 if (!staticConcat) { 1592 cb.aload(constantsSlot); 1593 } 1594 for (int i = 0; i < paramCount; i++) { 1595 var cl = concatArgs.parameterType(i); 1596 int paramSlot = cb.parameterSlot(i); 1597 var kind = TypeKind.from(cl); 1598 if (needStringOf(cl)) { 1599 paramSlot = stringSlots[i]; 1600 kind = TypeKind.REFERENCE; 1601 } 1602 cb.loadLocal(kind, paramSlot); 1603 } 1604 cb.invokestatic(concatClass, "prepend", prependArgs); 1605 1606 // return new String(buf, coder); 1607 cb.new_(CD_String) 1608 .dup() 1609 .aload(bufSlot) 1610 .iload(coderSlot) 1611 .invokespecial(CD_String, INIT_NAME, MTD_STRING_INIT) 1612 .areturn(); 1613 } 1614 }; 1615 } 1616 1617 /** 1618 * Generate length method. <p> 1619 * 1620 * The following is an example of the generated target code: 1621 * 1622 * <blockquote><pre> 1623 * import static java.lang.StringConcatHelper.stringSize; 1624 * 1625 * static int length(int length, int arg0, long arg1, boolean arg2, char arg3, 1626 * String arg4, String arg5, String arg6, String arg7) { 1627 * return stringSize(stringSize(stringSize(length, arg0), arg1), ..., arg7); 1628 * } 1629 * </pre></blockquote> 1630 */ 1631 private static Consumer<CodeBuilder> generateLengthMethod(MethodTypeDesc lengthArgs) { 1632 return new Consumer<CodeBuilder>() { 1633 @Override 1634 public void accept(CodeBuilder cb) { 1635 int lengthSlot = cb.parameterSlot(0); 1636 cb.iload(lengthSlot); 1637 for (int i = 1; i < lengthArgs.parameterCount(); i++) { 1638 var cl = lengthArgs.parameterType(i); 1639 MethodTypeDesc methodTypeDesc; 1640 if (cl == CD_char) { 1641 methodTypeDesc = MTD_int_int_char; 1642 } else if (cl == CD_int) { 1643 methodTypeDesc = MTD_int_int_int; 1644 } else if (cl == CD_long) { 1645 methodTypeDesc = MTD_int_int_long; 1646 } else if (cl == CD_boolean) { 1647 methodTypeDesc = MTD_int_int_boolean; 1648 } else { 1649 methodTypeDesc = MTD_int_int_String; 1650 } 1651 cb.loadLocal(TypeKind.from(cl), cb.parameterSlot(i)) 1652 .invokestatic(CD_StringConcatHelper, "stringSize", methodTypeDesc); 1653 } 1654 cb.ireturn(); 1655 } 1656 }; 1657 } 1658 1659 /** 1660 * Generate coder method. <p> 1661 * 1662 * The following is an example of the generated target code: 1663 * 1664 * <blockquote><pre> 1665 * import static java.lang.StringConcatHelper.stringCoder; 1666 * 1667 * static int cocder(int coder, char arg3, String str4, String str5, String str6, String str7) { 1668 * return coder | stringCoder(arg3) | str4.coder() | str5.coder() | str6.coder() | str7.coder(); 1669 * } 1670 * </pre></blockquote> 1671 */ 1672 private static Consumer<CodeBuilder> generateCoderMethod(MethodTypeDesc coderArgs) { 1673 return new Consumer<CodeBuilder>() { 1674 @Override 1675 public void accept(CodeBuilder cb) { 1676 /* 1677 * return coder | stringCoder(argN) | ... | arg1.coder() | arg0.coder(); 1678 */ 1679 int coderSlot = cb.parameterSlot(0); 1680 cb.iload(coderSlot); 1681 for (int i = 1; i < coderArgs.parameterCount(); i++) { 1682 var cl = coderArgs.parameterType(i); 1683 cb.loadLocal(TypeKind.from(cl), cb.parameterSlot(i)); 1684 if (cl == CD_char) { 1685 cb.invokestatic(CD_StringConcatHelper, "stringCoder", MTD_byte_char); 1686 } else { 1687 cb.invokevirtual(CD_String, "coder", MTD_byte); 1688 } 1689 cb.ior(); 1690 } 1691 cb.ireturn(); 1692 } 1693 }; 1694 } 1695 1696 /** 1697 * Generate prepend method. <p> 1698 * 1699 * The following is an example of the generated target code: 1700 * 1701 * <blockquote><pre> 1702 * import static java.lang.StringConcatHelper.prepend; 1703 * 1704 * static int prepend(int length, int coder, byte[] buf, String[] constants, 1705 * int arg0, long arg1, boolean arg2, char arg3, 1706 * String str4, String str5, String str6, String str7) { 1707 * 1708 * return prepend(prepend(prepend(prepend( 1709 * prepend(prepend(prepend(prepend(length, 1710 * buf, str7, constant[7]), buf, str6, constant[6]), 1711 * buf, str5, constant[5]), buf, str4, constant[4]), 1712 * buf, arg3, constant[3]), buf, arg2, constant[2]), 1713 * buf, arg1, constant[1]), buf, arg0, constant[0]); 1714 * } 1715 * </pre></blockquote> 1716 */ 1717 private static Consumer<CodeBuilder> generatePrependMethod( 1718 MethodTypeDesc prependArgs, 1719 boolean staticConcat, String[] constants 1720 ) { 1721 return new Consumer<CodeBuilder>() { 1722 @Override 1723 public void accept(CodeBuilder cb) { 1724 // Compute parameter variable slots 1725 int lengthSlot = cb.parameterSlot(0), 1726 coderSlot = cb.parameterSlot(1), 1727 bufSlot = cb.parameterSlot(2), 1728 constantsSlot = cb.parameterSlot(3); 1729 /* 1730 * // StringConcatHelper.prepend 1731 * return prepend(prepend(prepend(prepend( 1732 * prepend(apppend(prepend(prepend(length, 1733 * buf, str7, constant[7]), buf, str6, constant[6]), 1734 * buf, str5, constant[5]), buf, arg4, constant[4]), 1735 * buf, arg3, constant[3]), buf, arg2, constant[2]), 1736 * buf, arg1, constant[1]), buf, arg0, constant[0]); 1737 */ 1738 cb.iload(lengthSlot); 1739 for (int i = prependArgs.parameterCount() - 1, end = staticConcat ? 3 : 4; i >= end; i--) { 1740 var cl = prependArgs.parameterType(i); 1741 var kind = TypeKind.from(cl); 1742 1743 // There are only 5 types of parameters: int, long, boolean, char, String 1744 MethodTypeDesc methodTypeDesc; 1745 if (cl == CD_int) { 1746 methodTypeDesc = PREPEND_int; 1747 } else if (cl == CD_long) { 1748 methodTypeDesc = PREPEND_long; 1749 } else if (cl == CD_boolean) { 1750 methodTypeDesc = PREPEND_boolean; 1751 } else if (cl == CD_char) { 1752 methodTypeDesc = PREPEND_char; 1753 } else { 1754 kind = TypeKind.REFERENCE; 1755 methodTypeDesc = PREPEND_String; 1756 } 1757 1758 cb.iload(coderSlot) 1759 .aload(bufSlot) 1760 .loadLocal(kind, cb.parameterSlot(i)); 1761 1762 if (staticConcat) { 1763 cb.ldc(constants[i - 3]); 1764 } else { 1765 cb.aload(constantsSlot) 1766 .ldc(i - 4) 1767 .aaload(); 1768 } 1769 1770 cb.invokestatic(CD_StringConcatHelper, "prepend", methodTypeDesc); 1771 } 1772 cb.ireturn(); 1773 } 1774 }; 1775 } 1776 1777 static boolean needStringOf(Class<?> cl) { 1778 return cl != int.class && cl != long.class && cl != boolean.class && cl != char.class; 1779 } 1780 1781 static boolean maybeUTF16(Class<?> cl) { 1782 return cl == char.class || !cl.isPrimitive(); 1783 } 1784 } 1785 }