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