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