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 }