| /* | |
| * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. | |
| * Copyright (c) 2024, Alibaba Group Holding Limited. All Rights Reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package java.lang.invoke; | |
| import jdk.internal.access.JavaLangAccess; | |
| import jdk.internal.access.SharedSecrets; | |
| import jdk.internal.constant.ClassOrInterfaceDescImpl; | |
| import jdk.internal.constant.ConstantUtils; | |
| import jdk.internal.constant.MethodTypeDescImpl; | |
| import jdk.internal.misc.VM; | |
| import jdk.internal.util.ClassFileDumper; | |
| import jdk.internal.util.ReferenceKey; | |
| import jdk.internal.util.ReferencedKeyMap; | |
| import jdk.internal.vm.annotation.Stable; | |
| import sun.invoke.util.Wrapper; | |
| import java.lang.classfile.Annotation; | |
| import java.lang.classfile.ClassBuilder; | |
| import java.lang.classfile.ClassFile; | |
| import java.lang.classfile.CodeBuilder; | |
| import java.lang.classfile.MethodBuilder; | |
| import java.lang.classfile.TypeKind; | |
| import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute; | |
| import java.lang.constant.ClassDesc; | |
| import java.lang.constant.MethodTypeDesc; | |
| import java.lang.invoke.MethodHandles.Lookup; | |
| import java.lang.ref.SoftReference; | |
| import java.util.Map; | |
| import java.util.Objects; | |
| import java.util.concurrent.ConcurrentHashMap; | |
| import java.util.function.Consumer; | |
| import java.util.function.Supplier; | |
| import static java.lang.classfile.ClassFile.*; | |
| import static java.lang.constant.ConstantDescs.*; | |
| import static java.lang.invoke.MethodType.methodType; | |
| /** | |
| * <p>Methods to facilitate the creation of String concatenation methods, that | |
| * can be used to efficiently concatenate a known number of arguments of known | |
| * types, possibly after type adaptation and partial evaluation of arguments. | |
| * These methods are typically used as <em>bootstrap methods</em> for {@code | |
| * invokedynamic} call sites, to support the <em>string concatenation</em> | |
| * feature of the Java Programming Language. | |
| * | |
| * <p>Indirect access to the behavior specified by the provided {@code | |
| * MethodHandle} proceeds in order through two phases: | |
| * | |
| * <ol> | |
| * <li><em>Linkage</em> occurs when the methods in this class are invoked. | |
| * They take as arguments a method type describing the concatenated arguments | |
| * count and types, and optionally the String <em>recipe</em>, plus the | |
| * constants that participate in the String concatenation. The details on | |
| * accepted recipe shapes are described further below. Linkage may involve | |
| * dynamically loading a new class that implements the expected concatenation | |
| * behavior. The {@code CallSite} holds the {@code MethodHandle} pointing to the | |
| * exact concatenation method. The concatenation methods may be shared among | |
| * different {@code CallSite}s, e.g. if linkage methods produce them as pure | |
| * functions.</li> | |
| * | |
| * <li><em>Invocation</em> occurs when a generated concatenation method is | |
| * invoked with the exact dynamic arguments. This may occur many times for a | |
| * single concatenation method. The method referenced by the behavior {@code | |
| * MethodHandle} is invoked with the static arguments and any additional dynamic | |
| * arguments provided on invocation, as if by {@link MethodHandle#invoke(Object...)}.</li> | |
| * </ol> | |
| * | |
| * <p> This class provides two forms of linkage methods: a simple version | |
| * ({@link #makeConcat(java.lang.invoke.MethodHandles.Lookup, String, | |
| * MethodType)}) using only the dynamic arguments, and an advanced version | |
| * ({@link #makeConcatWithConstants(java.lang.invoke.MethodHandles.Lookup, | |
| * String, MethodType, String, Object...)} using the advanced forms of capturing | |
| * the constant arguments. The advanced strategy can produce marginally better | |
| * invocation bytecode, at the expense of exploding the number of shapes of | |
| * string concatenation methods present at runtime, because those shapes would | |
| * include constant static arguments as well. | |
| * | |
| * @author Aleksey Shipilev | |
| * @author Remi Forax | |
| * @author Peter Levart | |
| * | |
| * @apiNote | |
| * <p>There is a JVM limit (classfile structural constraint): no method | |
| * can call with more than 255 slots. This limits the number of static and | |
| * dynamic arguments one can pass to bootstrap method. Since there are potential | |
| * concatenation strategies that use {@code MethodHandle} combinators, we need | |
| * to reserve a few empty slots on the parameter lists to capture the | |
| * temporal results. This is why bootstrap methods in this factory do not accept | |
| * more than 200 argument slots. Users requiring more than 200 argument slots in | |
| * concatenation are expected to split the large concatenation in smaller | |
| * expressions. | |
| * | |
| * @since 9 | |
| */ | |
| public final class StringConcatFactory { | |
| private static final int HIGH_ARITY_THRESHOLD; | |
| private static final int CACHE_THRESHOLD; | |
| private static final int FORCE_INLINE_THRESHOLD; | |
| static { | |
| String highArity = VM.getSavedProperty("java.lang.invoke.StringConcat.highArityThreshold"); | |
| HIGH_ARITY_THRESHOLD = highArity != null ? Integer.parseInt(highArity) : 0; | |
| String cacheThreshold = VM.getSavedProperty("java.lang.invoke.StringConcat.cacheThreshold"); | |
| CACHE_THRESHOLD = cacheThreshold != null ? Integer.parseInt(cacheThreshold) : 256; | |
| String inlineThreshold = VM.getSavedProperty("java.lang.invoke.StringConcat.inlineThreshold"); | |
| FORCE_INLINE_THRESHOLD = inlineThreshold != null ? Integer.parseInt(inlineThreshold) : 16; | |
| } | |
| /** | |
| * Tag used to demarcate an ordinary argument. | |
| */ | |
| private static final char TAG_ARG = '\u0001'; | |
| /** | |
| * Tag used to demarcate a constant. | |
| */ | |
| private static final char TAG_CONST = '\u0002'; | |
| /** | |
| * Maximum number of argument slots in String Concat call. | |
| * | |
| * While the maximum number of argument slots that indy call can handle is 253, | |
| * we do not use all those slots, to let the strategies with MethodHandle | |
| * combinators to use some arguments. | |
| */ | |
| private static final int MAX_INDY_CONCAT_ARG_SLOTS = 200; | |
| private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); | |
| // StringConcatFactory bootstrap methods are startup sensitive, and may be | |
| // special cased in java.lang.invoke.BootstrapMethodInvoker to ensure | |
| // methods are invoked with exact type information to avoid generating | |
| // code for runtime checks. Take care any changes or additions here are | |
| // reflected there as appropriate. | |
| /** | |
| * Facilitates the creation of optimized String concatenation methods, that | |
| * can be used to efficiently concatenate a known number of arguments of | |
| * known types, possibly after type adaptation and partial evaluation of | |
| * arguments. Typically used as a <em>bootstrap method</em> for {@code | |
| * invokedynamic} call sites, to support the <em>string concatenation</em> | |
| * feature of the Java Programming Language. | |
| * | |
| * <p>When the target of the {@code CallSite} returned from this method is | |
| * invoked, it returns the result of String concatenation, taking all | |
| * function arguments passed to the linkage method as inputs for | |
| * concatenation. The target signature is given by {@code concatType}. | |
| * For a target accepting: | |
| * <ul> | |
| * <li>zero inputs, concatenation results in an empty string;</li> | |
| * <li>one input, concatenation results in the single | |
| * input converted as per JLS {@jls 5.1.11} "String Conversion"; otherwise</li> | |
| * <li>two or more inputs, the inputs are concatenated as per | |
| * requirements stated in JLS {@jls 15.18.1} "String Concatenation Operator +". | |
| * The inputs are converted as per JLS {@jls 5.1.11} "String Conversion", | |
| * and combined from left to right.</li> | |
| * </ul> | |
| * | |
| * <p>Assume the linkage arguments are as follows: | |
| * | |
| * <ul> | |
| * <li>{@code concatType}, describing the {@code CallSite} signature</li> | |
| * </ul> | |
| * | |
| * <p>Then the following linkage invariants must hold: | |
| * | |
| * <ul> | |
| * <li>The number of parameter slots in {@code concatType} is | |
| * less than or equal to 200</li> | |
| * <li>The return type in {@code concatType} is assignable from {@link java.lang.String}</li> | |
| * </ul> | |
| * | |
| * @param lookup Represents a lookup context with the accessibility | |
| * privileges of the caller. Specifically, the lookup | |
| * context must have | |
| * {@linkplain MethodHandles.Lookup#hasFullPrivilegeAccess() | |
| * full privilege access}. | |
| * When used with {@code invokedynamic}, this is stacked | |
| * automatically by the VM. | |
| * @param name The name of the method to implement. This name is | |
| * arbitrary, and has no meaning for this linkage method. | |
| * When used with {@code invokedynamic}, this is provided by | |
| * the {@code NameAndType} of the {@code InvokeDynamic} | |
| * structure and is stacked automatically by the VM. | |
| * @param concatType The expected signature of the {@code CallSite}. The | |
| * parameter types represent the types of concatenation | |
| * arguments; the return type is always assignable from {@link | |
| * java.lang.String}. When used with {@code invokedynamic}, | |
| * this is provided by the {@code NameAndType} of the {@code | |
| * InvokeDynamic} structure and is stacked automatically by | |
| * the VM. | |
| * @return a CallSite whose target can be used to perform String | |
| * concatenation, with dynamic concatenation arguments described by the given | |
| * {@code concatType}. | |
| * @throws StringConcatException If any of the linkage invariants described | |
| * here are violated, or the lookup context | |
| * does not have private access privileges. | |
| * @throws NullPointerException If any of the incoming arguments is null. | |
| * This will never happen when a bootstrap method | |
| * is called with invokedynamic. | |
| * | |
| * @jls 5.1.11 String Conversion | |
| * @jls 15.18.1 String Concatenation Operator + | |
| */ | |
| public static CallSite makeConcat(MethodHandles.Lookup lookup, | |
| String name, | |
| MethodType concatType) throws StringConcatException { | |
| // This bootstrap method is unlikely to be used in practice, | |
| // avoid optimizing it at the expense of makeConcatWithConstants | |
| // Mock the recipe to reuse the concat generator code | |
| String recipe = "\u0001".repeat(concatType.parameterCount()); | |
| return makeConcatWithConstants(lookup, name, concatType, recipe); | |
| } | |
| /** | |
| * Facilitates the creation of optimized String concatenation methods, that | |
| * can be used to efficiently concatenate a known number of arguments of | |
| * known types, possibly after type adaptation and partial evaluation of | |
| * arguments. Typically used as a <em>bootstrap method</em> for {@code | |
| * invokedynamic} call sites, to support the <em>string concatenation</em> | |
| * feature of the Java Programming Language. | |
| * | |
| * <p>When the target of the {@code CallSite} returned from this method is | |
| * invoked, it returns the result of String concatenation, taking all | |
| * function arguments and constants passed to the linkage method as inputs for | |
| * concatenation. The target signature is given by {@code concatType}, and | |
| * does not include constants. | |
| * For a target accepting: | |
| * <ul> | |
| * <li>zero inputs, concatenation results in an empty string;</li> | |
| * <li>one input, concatenation results in the single | |
| * input converted as per JLS {@jls 5.1.11} "String Conversion"; otherwise</li> | |
| * <li>two or more inputs, the inputs are concatenated as per | |
| * requirements stated in JLS {@jls 15.18.1} "String Concatenation Operator +". | |
| * The inputs are converted as per JLS {@jls 5.1.11} "String Conversion", | |
| * and combined from left to right.</li> | |
| * </ul> | |
| * | |
| * <p>The concatenation <em>recipe</em> is a String description for the way to | |
| * construct a concatenated String from the arguments and constants. The | |
| * recipe is processed from left to right, and each character represents an | |
| * input to concatenation. Recipe characters mean: | |
| * | |
| * <ul> | |
| * | |
| * <li><em>\1 (Unicode point 0001)</em>: an ordinary argument. This | |
| * input is passed through dynamic argument, and is provided during the | |
| * concatenation method invocation. This input can be null.</li> | |
| * | |
| * <li><em>\2 (Unicode point 0002):</em> a constant. This input passed | |
| * through static bootstrap argument. This constant can be any value | |
| * representable in constant pool. If necessary, the factory would call | |
| * {@code toString} to perform a one-time String conversion.</li> | |
| * | |
| * <li><em>Any other char value:</em> a single character constant.</li> | |
| * </ul> | |
| * | |
| * <p>Assume the linkage arguments are as follows: | |
| * | |
| * <ul> | |
| * <li>{@code concatType}, describing the {@code CallSite} signature</li> | |
| * <li>{@code recipe}, describing the String recipe</li> | |
| * <li>{@code constants}, the vararg array of constants</li> | |
| * </ul> | |
| * | |
| * <p>Then the following linkage invariants must hold: | |
| * | |
| * <ul> | |
| * <li>The number of parameter slots in {@code concatType} is less than | |
| * or equal to 200</li> | |
| * | |
| * <li>The parameter count in {@code concatType} is equal to number of \1 tags | |
| * in {@code recipe}</li> | |
| * | |
| * <li>The return type in {@code concatType} is assignable | |
| * from {@link java.lang.String}, and matches the return type of the | |
| * returned {@link MethodHandle}</li> | |
| * | |
| * <li>The number of elements in {@code constants} is equal to number of \2 | |
| * tags in {@code recipe}</li> | |
| * </ul> | |
| * | |
| * @param lookup Represents a lookup context with the accessibility | |
| * privileges of the caller. Specifically, the lookup | |
| * context must have | |
| * {@linkplain MethodHandles.Lookup#hasFullPrivilegeAccess() | |
| * full privilege access}. | |
| * When used with {@code invokedynamic}, this is stacked | |
| * automatically by the VM. | |
| * @param name The name of the method to implement. This name is | |
| * arbitrary, and has no meaning for this linkage method. | |
| * When used with {@code invokedynamic}, this is provided | |
| * by the {@code NameAndType} of the {@code InvokeDynamic} | |
| * structure and is stacked automatically by the VM. | |
| * @param concatType The expected signature of the {@code CallSite}. The | |
| * parameter types represent the types of dynamic concatenation | |
| * arguments; the return type is always assignable from {@link | |
| * java.lang.String}. When used with {@code | |
| * invokedynamic}, this is provided by the {@code | |
| * NameAndType} of the {@code InvokeDynamic} structure and | |
| * is stacked automatically by the VM. | |
| * @param recipe Concatenation recipe, described above. | |
| * @param constants A vararg parameter representing the constants passed to | |
| * the linkage method. | |
| * @return a CallSite whose target can be used to perform String | |
| * concatenation, with dynamic concatenation arguments described by the given | |
| * {@code concatType}. | |
| * @throws StringConcatException If any of the linkage invariants described | |
| * here are violated, or the lookup context | |
| * does not have private access privileges. | |
| * @throws NullPointerException If any of the incoming arguments is null, or | |
| * any constant in {@code recipe} is null. | |
| * This will never happen when a bootstrap method | |
| * is called with invokedynamic. | |
| * @apiNote Code generators have three distinct ways to process a constant | |
| * string operand S in a string concatenation expression. First, S can be | |
| * materialized as a reference (using ldc) and passed as an ordinary argument | |
| * (recipe '\1'). Or, S can be stored in the constant pool and passed as a | |
| * constant (recipe '\2') . Finally, if S contains neither of the recipe | |
| * tag characters ('\1', '\2') then S can be interpolated into the recipe | |
| * itself, causing its characters to be inserted into the result. | |
| * | |
| * @jls 5.1.11 String Conversion | |
| * @jls 15.18.1 String Concatenation Operator + | |
| */ | |
| public static CallSite makeConcatWithConstants(MethodHandles.Lookup lookup, | |
| String name, | |
| MethodType concatType, | |
| String recipe, | |
| Object... constants) | |
| throws StringConcatException | |
| { | |
| Objects.requireNonNull(lookup, "Lookup is null"); | |
| Objects.requireNonNull(name, "Name is null"); | |
| Objects.requireNonNull(recipe, "Recipe is null"); | |
| Objects.requireNonNull(concatType, "Concat type is null"); | |
| Objects.requireNonNull(constants, "Constants are null"); | |
| for (Object o : constants) { | |
| Objects.requireNonNull(o, "Cannot accept null constants"); | |
| } | |
| if ((lookup.lookupModes() & MethodHandles.Lookup.PRIVATE) == 0) { | |
| throw new StringConcatException("Invalid caller: " + | |
| lookup.lookupClass().getName()); | |
| } | |
| String[] constantStrings = parseRecipe(concatType, recipe, constants); | |
| if (!concatType.returnType().isAssignableFrom(String.class)) { | |
| throw new StringConcatException( | |
| "The return type should be compatible with String, but it is " + | |
| concatType.returnType()); | |
| } | |
| if (concatType.parameterSlotCount() > MAX_INDY_CONCAT_ARG_SLOTS) { | |
| throw new StringConcatException("Too many concat argument slots: " + | |
| concatType.parameterSlotCount() + | |
| ", can only accept " + | |
| MAX_INDY_CONCAT_ARG_SLOTS); | |
| } | |
| try { | |
| MethodHandle mh = makeSimpleConcat(concatType, constantStrings); | |
| if (mh == null && concatType.parameterCount() <= HIGH_ARITY_THRESHOLD) { | |
| mh = generateMHInlineCopy(concatType, constantStrings); | |
| } | |
| if (mh == null) { | |
| mh = InlineHiddenClassStrategy.generate(lookup, concatType, constantStrings); | |
| } | |
| mh = mh.viewAsType(concatType, true); | |
| return new ConstantCallSite(mh); | |
| } catch (Error e) { | |
| // Pass through any error | |
| throw e; | |
| } catch (Throwable t) { | |
| throw new StringConcatException("Generator failed", t); | |
| } | |
| } | |
| private static String[] parseRecipe(MethodType concatType, | |
| String recipe, | |
| Object[] constants) | |
| throws StringConcatException | |
| { | |
| Objects.requireNonNull(recipe, "Recipe is null"); | |
| int paramCount = concatType.parameterCount(); | |
| // Array containing interleaving String constants, starting with | |
| // the first prefix and ending with the final prefix: | |
| // | |
| // consts[0] + arg0 + consts[1] + arg 1 + ... + consts[paramCount]. | |
| // | |
| // consts will be null if there's no constant to insert at a position. | |
| // An empty String constant will be replaced by null. | |
| String[] consts = new String[paramCount + 1]; | |
| int cCount = 0; | |
| int oCount = 0; | |
| StringBuilder acc = new StringBuilder(); | |
| for (int i = 0; i < recipe.length(); i++) { | |
| char c = recipe.charAt(i); | |
| if (c == TAG_CONST) { | |
| if (cCount == constants.length) { | |
| // Not enough constants | |
| throw constantMismatch(constants, cCount); | |
| } | |
| // Accumulate constant args along with any constants encoded | |
| // into the recipe | |
| acc.append(constants[cCount++]); | |
| } else if (c == TAG_ARG) { | |
| // Check for overflow | |
| if (oCount >= paramCount) { | |
| throw argumentMismatch(concatType, oCount); | |
| } | |
| // Flush any accumulated characters into a constant | |
| consts[oCount++] = acc.length() > 0 ? acc.toString() : ""; | |
| acc.setLength(0); | |
| } else { | |
| // Not a special character, this is a constant embedded into | |
| // the recipe itself. | |
| acc.append(c); | |
| } | |
| } | |
| if (oCount != concatType.parameterCount()) { | |
| throw argumentMismatch(concatType, oCount); | |
| } | |
| if (cCount < constants.length) { | |
| throw constantMismatch(constants, cCount); | |
| } | |
| // Flush the remaining characters as constant: | |
| consts[oCount] = acc.length() > 0 ? acc.toString() : ""; | |
| return consts; | |
| } | |
| private static StringConcatException argumentMismatch(MethodType concatType, | |
| int oCount) { | |
| return new StringConcatException( | |
| "Mismatched number of concat arguments: recipe wants " + | |
| oCount + | |
| " arguments, but signature provides " + | |
| concatType.parameterCount()); | |
| } | |
| private static StringConcatException constantMismatch(Object[] constants, | |
| int cCount) { | |
| return new StringConcatException( | |
| "Mismatched number of concat constants: recipe wants " + | |
| cCount + | |
| " constants, but only " + | |
| constants.length + | |
| " are passed"); | |
| } | |
| private static MethodHandle makeSimpleConcat(MethodType mt, String[] constants) { | |
| int paramCount = mt.parameterCount(); | |
| String suffix = constants[paramCount]; | |
| // Fast-path trivial concatenations | |
| if (paramCount == 0) { | |
| return MethodHandles.insertArguments(newStringifier(), 0, suffix == null ? "" : suffix); | |
| } | |
| if (paramCount == 1) { | |
| String prefix = constants[0]; | |
| // Empty constants will be | |
| if (prefix.isEmpty()) { | |
| if (suffix.isEmpty()) { | |
| return unaryConcat(mt.parameterType(0)); | |
| } else if (!mt.hasPrimitives()) { | |
| return MethodHandles.insertArguments(simpleConcat(), 1, suffix); | |
| } // else fall-through | |
| } else if (suffix.isEmpty() && !mt.hasPrimitives()) { | |
| // Non-primitive argument | |
| return MethodHandles.insertArguments(simpleConcat(), 0, prefix); | |
| } // fall-through if there's both a prefix and suffix | |
| } else if (paramCount == 2 && !mt.hasPrimitives() && suffix.isEmpty() | |
| && constants[0].isEmpty() && constants[1].isEmpty()) { | |
| // Two reference arguments, no surrounding constants | |
| return simpleConcat(); | |
| } | |
| return null; | |
| } | |
| /** | |
| * <p>This strategy replicates what StringBuilders are doing: it builds the | |
| * byte[] array on its own and passes that byte[] array to String | |
| * constructor. This strategy requires access to some private APIs in JDK, | |
| * most notably, the private String constructor that accepts byte[] arrays | |
| * without copying. | |
| */ | |
| private static MethodHandle generateMHInlineCopy(MethodType mt, String[] constants) { | |
| int paramCount = mt.parameterCount(); | |
| String suffix = constants[paramCount]; | |
| // else... fall-through to slow-path | |
| // Create filters and obtain filtered parameter types. Filters would be used in the beginning | |
| // to convert the incoming arguments into the arguments we can process (e.g. Objects -> Strings). | |
| // The filtered argument type list is used all over in the combinators below. | |
| Class<?>[] ptypes = mt.erase().parameterArray(); | |
| MethodHandle[] objFilters = null; | |
| MethodHandle[] floatFilters = null; | |
| MethodHandle[] doubleFilters = null; | |
| for (int i = 0; i < ptypes.length; i++) { | |
| Class<?> cl = ptypes[i]; | |
| // Use int as the logical type for subword integral types | |
| // (byte and short). char and boolean require special | |
| // handling so don't change the logical type of those | |
| ptypes[i] = promoteToIntType(ptypes[i]); | |
| // Object, float and double will be eagerly transformed | |
| // into a (non-null) String as a first step after invocation. | |
| // Set up to use String as the logical type for such arguments | |
| // internally. | |
| if (cl == Object.class) { | |
| if (objFilters == null) { | |
| objFilters = new MethodHandle[ptypes.length]; | |
| } | |
| objFilters[i] = objectStringifier(); | |
| ptypes[i] = String.class; | |
| } else if (cl == float.class) { | |
| if (floatFilters == null) { | |
| floatFilters = new MethodHandle[ptypes.length]; | |
| } | |
| floatFilters[i] = floatStringifier(); | |
| ptypes[i] = String.class; | |
| } else if (cl == double.class) { | |
| if (doubleFilters == null) { | |
| doubleFilters = new MethodHandle[ptypes.length]; | |
| } | |
| doubleFilters[i] = doubleStringifier(); | |
| ptypes[i] = String.class; | |
| } | |
| } | |
| // Start building the combinator tree. The tree "starts" with (<parameters>)String, and "finishes" | |
| // with the (byte[], long)String shape to invoke newString in StringConcatHelper. The combinators are | |
| // assembled bottom-up, which makes the code arguably hard to read. | |
| // Drop all remaining parameter types, leave only helper arguments: | |
| MethodHandle mh = MethodHandles.dropArgumentsTrusted(newString(), 2, ptypes); | |
| // Calculate the initialLengthCoder value by looking at all constant values and summing up | |
| // their lengths and adjusting the encoded coder bit if needed | |
| long initialLengthCoder = INITIAL_CODER; | |
| for (String constant : constants) { | |
| if (constant != null) { | |
| initialLengthCoder = JLA.stringConcatMix(initialLengthCoder, constant); | |
| } | |
| } | |
| // Mix in prependers. This happens when (byte[], long) = (storage, indexCoder) is already | |
| // known from the combinators below. We are assembling the string backwards, so the index coded | |
| // into indexCoder is the *ending* index. | |
| mh = filterInPrependers(mh, constants, ptypes); | |
| // Fold in byte[] instantiation at argument 0 | |
| MethodHandle newArrayCombinator; | |
| if (suffix == null || suffix.isEmpty()) { | |
| suffix = ""; | |
| } | |
| // newArray variant that deals with prepending any trailing constant | |
| // | |
| // initialLengthCoder is adjusted to have the correct coder | |
| // and length: The newArrayWithSuffix method expects only the coder of the | |
| // suffix to be encoded into indexCoder | |
| initialLengthCoder -= suffix.length(); | |
| newArrayCombinator = newArrayWithSuffix(suffix); | |
| mh = MethodHandles.foldArgumentsWithCombiner(mh, 0, newArrayCombinator, | |
| 1 // index | |
| ); | |
| // Start combining length and coder mixers. | |
| // | |
| // Length is easy: constant lengths can be computed on the spot, and all non-constant | |
| // shapes have been either converted to Strings, or explicit methods for getting the | |
| // string length out of primitives are provided. | |
| // | |
| // Coders are more interesting. Only Object, String and char arguments (and constants) | |
| // can have non-Latin1 encoding. It is easier to blindly convert constants to String, | |
| // and deduce the coder from there. Arguments would be either converted to Strings | |
| // during the initial filtering, or handled by specializations in MIXERS. | |
| // | |
| // The method handle shape before all mixers are combined in is: | |
| // (long, <args>)String = ("indexCoder", <args>) | |
| // | |
| // We will bind the initialLengthCoder value to the last mixer (the one that will be | |
| // executed first), then fold that in. This leaves the shape after all mixers are | |
| // combined in as: | |
| // (<args>)String = (<args>) | |
| mh = filterAndFoldInMixers(mh, initialLengthCoder, ptypes); | |
| // The method handle shape here is (<args>). | |
| // Apply filters, converting the arguments: | |
| if (objFilters != null) { | |
| mh = MethodHandles.filterArguments(mh, 0, objFilters); | |
| } | |
| if (floatFilters != null) { | |
| mh = MethodHandles.filterArguments(mh, 0, floatFilters); | |
| } | |
| if (doubleFilters != null) { | |
| mh = MethodHandles.filterArguments(mh, 0, doubleFilters); | |
| } | |
| return mh; | |
| } | |
| // We need one prepender per argument, but also need to fold in constants. We do so by greedily | |
| // creating prependers that fold in surrounding constants into the argument prepender. This reduces | |
| // the number of unique MH combinator tree shapes we'll create in an application. | |
| // Additionally we do this in chunks to reduce the number of combinators bound to the root tree, | |
| // which simplifies the shape and makes construction of similar trees use less unique LF classes | |
| private static MethodHandle filterInPrependers(MethodHandle mh, String[] constants, Class<?>[] ptypes) { | |
| int pos; | |
| int[] argPositions = null; | |
| MethodHandle prepend; | |
| for (pos = 0; pos < ptypes.length - 3; pos += 4) { | |
| prepend = prepender(pos, constants, ptypes, 4); | |
| argPositions = filterPrependArgPositions(argPositions, pos, 4); | |
| mh = MethodHandles.filterArgumentsWithCombiner(mh, 1, prepend, argPositions); | |
| } | |
| if (pos < ptypes.length) { | |
| int count = ptypes.length - pos; | |
| prepend = prepender(pos, constants, ptypes, count); | |
| argPositions = filterPrependArgPositions(argPositions, pos, count); | |
| mh = MethodHandles.filterArgumentsWithCombiner(mh, 1, prepend, argPositions); | |
| } | |
| return mh; | |
| } | |
| static int[] filterPrependArgPositions(int[] argPositions, int pos, int count) { | |
| if (argPositions == null || argPositions.length != count + 2) { | |
| argPositions = new int[count + 2]; | |
| argPositions[0] = 1; // indexCoder | |
| argPositions[1] = 0; // storage | |
| } | |
| int limit = count + 2; | |
| for (int i = 2; i < limit; i++) { | |
| argPositions[i] = i + pos; | |
| } | |
| return argPositions; | |
| } | |
| // We need one mixer per argument. | |
| private static MethodHandle filterAndFoldInMixers(MethodHandle mh, long initialLengthCoder, Class<?>[] ptypes) { | |
| int pos; | |
| int[] argPositions = null; | |
| for (pos = 0; pos < ptypes.length - 4; pos += 4) { | |
| // Compute new "index" in-place pairwise using old value plus the appropriate arguments. | |
| MethodHandle mix = mixer(ptypes[pos], ptypes[pos + 1], ptypes[pos + 2], ptypes[pos + 3]); | |
| argPositions = filterMixerArgPositions(argPositions, pos, 4); | |
| mh = MethodHandles.filterArgumentsWithCombiner(mh, 0, | |
| mix, argPositions); | |
| } | |
| if (pos < ptypes.length) { | |
| // Mix in the last 1 to 4 parameters, insert the initialLengthCoder into the final mixer and | |
| // fold the result into the main combinator | |
| mh = foldInLastMixers(mh, initialLengthCoder, pos, ptypes, ptypes.length - pos); | |
| } else if (ptypes.length == 0) { | |
| // No mixer (constants only concat), insert initialLengthCoder directly | |
| mh = MethodHandles.insertArguments(mh, 0, initialLengthCoder); | |
| } | |
| return mh; | |
| } | |
| static int[] filterMixerArgPositions(int[] argPositions, int pos, int count) { | |
| if (argPositions == null || argPositions.length != count + 2) { | |
| argPositions = new int[count + 1]; | |
| argPositions[0] = 0; // indexCoder | |
| } | |
| int limit = count + 1; | |
| for (int i = 1; i < limit; i++) { | |
| argPositions[i] = i + pos; | |
| } | |
| return argPositions; | |
| } | |
| private static MethodHandle foldInLastMixers(MethodHandle mh, long initialLengthCoder, int pos, Class<?>[] ptypes, int count) { | |
| MethodHandle mix = switch (count) { | |
| case 1 -> mixer(ptypes[pos]); | |
| case 2 -> mixer(ptypes[pos], ptypes[pos + 1]); | |
| case 3 -> mixer(ptypes[pos], ptypes[pos + 1], ptypes[pos + 2]); | |
| case 4 -> mixer(ptypes[pos], ptypes[pos + 1], ptypes[pos + 2], ptypes[pos + 3]); | |
| default -> throw new IllegalArgumentException("Unexpected count: " + count); | |
| }; | |
| mix = MethodHandles.insertArguments(mix,0, initialLengthCoder); | |
| // apply selected arguments on the 1-4 arg mixer and fold in the result | |
| return switch (count) { | |
| case 1 -> MethodHandles.foldArgumentsWithCombiner(mh, 0, mix, | |
| 1 + pos); | |
| case 2 -> MethodHandles.foldArgumentsWithCombiner(mh, 0, mix, | |
| 1 + pos, 2 + pos); | |
| case 3 -> MethodHandles.foldArgumentsWithCombiner(mh, 0, mix, | |
| 1 + pos, 2 + pos, 3 + pos); | |
| case 4 -> MethodHandles.foldArgumentsWithCombiner(mh, 0, mix, | |
| 1 + pos, 2 + pos, 3 + pos, 4 + pos); | |
| default -> throw new IllegalArgumentException(); | |
| }; | |
| } | |
| // Simple prependers, single argument. May be used directly or as a | |
| // building block for complex prepender combinators. | |
| private static MethodHandle prepender(String prefix, Class<?> cl) { | |
| if (prefix == null || prefix.isEmpty()) { | |
| return noPrefixPrepender(cl); | |
| } else { | |
| return MethodHandles.insertArguments( | |
| prepender(cl), 3, prefix); | |
| } | |
| } | |
| private static MethodHandle prepender(Class<?> cl) { | |
| int idx = classIndex(cl); | |
| MethodHandle prepend = PREPENDERS[idx]; | |
| if (prepend == null) { | |
| PREPENDERS[idx] = prepend = JLA.stringConcatHelper("prepend", | |
| methodType(long.class, long.class, byte[].class, | |
| Wrapper.asPrimitiveType(cl), String.class)).rebind(); | |
| } | |
| return prepend; | |
| } | |
| private static MethodHandle noPrefixPrepender(Class<?> cl) { | |
| int idx = classIndex(cl); | |
| MethodHandle prepend = NO_PREFIX_PREPENDERS[idx]; | |
| if (prepend == null) { | |
| NO_PREFIX_PREPENDERS[idx] = prepend = MethodHandles.insertArguments(prepender(cl), 3, ""); | |
| } | |
| return prepend; | |
| } | |
| private static final int INT_IDX = 0, | |
| CHAR_IDX = 1, | |
| LONG_IDX = 2, | |
| BOOLEAN_IDX = 3, | |
| STRING_IDX = 4, | |
| TYPE_COUNT = 5; | |
| private static int classIndex(Class<?> cl) { | |
| if (cl == String.class) return STRING_IDX; | |
| if (cl == int.class) return INT_IDX; | |
| if (cl == boolean.class) return BOOLEAN_IDX; | |
| if (cl == char.class) return CHAR_IDX; | |
| if (cl == long.class) return LONG_IDX; | |
| throw new IllegalArgumentException("Unexpected class: " + cl); | |
| } | |
| // Constant argument lists used by the prepender MH builders | |
| private static final int[] PREPEND_FILTER_FIRST_ARGS = new int[] { 0, 1, 2 }; | |
| private static final int[] PREPEND_FILTER_SECOND_ARGS = new int[] { 0, 1, 3 }; | |
| private static final int[] PREPEND_FILTER_THIRD_ARGS = new int[] { 0, 1, 4 }; | |
| private static final int[] PREPEND_FILTER_FIRST_PAIR_ARGS = new int[] { 0, 1, 2, 3 }; | |
| private static final int[] PREPEND_FILTER_SECOND_PAIR_ARGS = new int[] { 0, 1, 4, 5 }; | |
| // Base MH for complex prepender combinators. | |
| private static @Stable MethodHandle PREPEND_BASE; | |
| private static MethodHandle prependBase() { | |
| MethodHandle base = PREPEND_BASE; | |
| if (base == null) { | |
| base = PREPEND_BASE = MethodHandles.dropArguments( | |
| MethodHandles.identity(long.class), 1, byte[].class); | |
| } | |
| return base; | |
| } | |
| private static final @Stable MethodHandle[][] DOUBLE_PREPENDERS = new MethodHandle[TYPE_COUNT][TYPE_COUNT]; | |
| private static MethodHandle prepender(String prefix, Class<?> cl, String prefix2, Class<?> cl2) { | |
| int idx1 = classIndex(cl); | |
| int idx2 = classIndex(cl2); | |
| MethodHandle prepend = DOUBLE_PREPENDERS[idx1][idx2]; | |
| if (prepend == null) { | |
| prepend = DOUBLE_PREPENDERS[idx1][idx2] = | |
| MethodHandles.dropArguments(prependBase(), 2, cl, cl2); | |
| } | |
| prepend = MethodHandles.filterArgumentsWithCombiner(prepend, 0, prepender(prefix, cl), | |
| PREPEND_FILTER_FIRST_ARGS); | |
| return MethodHandles.filterArgumentsWithCombiner(prepend, 0, prepender(prefix2, cl2), | |
| PREPEND_FILTER_SECOND_ARGS); | |
| } | |
| private static MethodHandle prepender(int pos, String[] constants, Class<?>[] ptypes, int count) { | |
| // build the simple cases directly | |
| if (count == 1) { | |
| return prepender(constants[pos], ptypes[pos]); | |
| } | |
| if (count == 2) { | |
| return prepender(constants[pos], ptypes[pos], constants[pos + 1], ptypes[pos + 1]); | |
| } | |
| // build a tree from an unbound prepender, allowing us to bind the constants in a batch as a final step | |
| MethodHandle prepend = prependBase(); | |
| if (count == 3) { | |
| prepend = MethodHandles.dropArguments(prepend, 2, | |
| ptypes[pos], ptypes[pos + 1], ptypes[pos + 2]); | |
| prepend = MethodHandles.filterArgumentsWithCombiner(prepend, 0, | |
| prepender(constants[pos], ptypes[pos], constants[pos + 1], ptypes[pos + 1]), | |
| PREPEND_FILTER_FIRST_PAIR_ARGS); | |
| return MethodHandles.filterArgumentsWithCombiner(prepend, 0, | |
| prepender(constants[pos + 2], ptypes[pos + 2]), | |
| PREPEND_FILTER_THIRD_ARGS); | |
| } else if (count == 4) { | |
| prepend = MethodHandles.dropArguments(prepend, 2, | |
| ptypes[pos], ptypes[pos + 1], ptypes[pos + 2], ptypes[pos + 3]); | |
| prepend = MethodHandles.filterArgumentsWithCombiner(prepend, 0, | |
| prepender(constants[pos], ptypes[pos], constants[pos + 1], ptypes[pos + 1]), | |
| PREPEND_FILTER_FIRST_PAIR_ARGS); | |
| return MethodHandles.filterArgumentsWithCombiner(prepend, 0, | |
| prepender(constants[pos + 2], ptypes[pos + 2], constants[pos + 3], ptypes[pos + 3]), | |
| PREPEND_FILTER_SECOND_PAIR_ARGS); | |
| } else { | |
| throw new IllegalArgumentException("Unexpected count: " + count); | |
| } | |
| } | |
| // Constant argument lists used by the mixer MH builders | |
| private static final int[] MIX_FILTER_SECOND_ARGS = new int[] { 0, 2 }; | |
| private static final int[] MIX_FILTER_THIRD_ARGS = new int[] { 0, 3 }; | |
| private static final int[] MIX_FILTER_SECOND_PAIR_ARGS = new int[] { 0, 3, 4 }; | |
| private static MethodHandle mixer(Class<?> cl) { | |
| int index = classIndex(cl); | |
| MethodHandle mix = MIXERS[index]; | |
| if (mix == null) { | |
| MIXERS[index] = mix = JLA.stringConcatHelper("mix", | |
| methodType(long.class, long.class, Wrapper.asPrimitiveType(cl))).rebind(); | |
| } | |
| return mix; | |
| } | |
| private static final @Stable MethodHandle[][] DOUBLE_MIXERS = new MethodHandle[TYPE_COUNT][TYPE_COUNT]; | |
| private static MethodHandle mixer(Class<?> cl, Class<?> cl2) { | |
| int idx1 = classIndex(cl); | |
| int idx2 = classIndex(cl2); | |
| MethodHandle mix = DOUBLE_MIXERS[idx1][idx2]; | |
| if (mix == null) { | |
| mix = mixer(cl); | |
| mix = MethodHandles.dropArguments(mix, 2, cl2); | |
| DOUBLE_MIXERS[idx1][idx2] = mix = MethodHandles.filterArgumentsWithCombiner(mix, 0, | |
| mixer(cl2), MIX_FILTER_SECOND_ARGS); | |
| } | |
| return mix; | |
| } | |
| private static MethodHandle mixer(Class<?> cl, Class<?> cl2, Class<?> cl3) { | |
| MethodHandle mix = mixer(cl, cl2); | |
| mix = MethodHandles.dropArguments(mix, 3, cl3); | |
| return MethodHandles.filterArgumentsWithCombiner(mix, 0, | |
| mixer(cl3), MIX_FILTER_THIRD_ARGS); | |
| } | |
| private static MethodHandle mixer(Class<?> cl, Class<?> cl2, Class<?> cl3, Class<?> cl4) { | |
| MethodHandle mix = mixer(cl, cl2); | |
| mix = MethodHandles.dropArguments(mix, 3, cl3, cl4); | |
| return MethodHandles.filterArgumentsWithCombiner(mix, 0, | |
| mixer(cl3, cl4), MIX_FILTER_SECOND_PAIR_ARGS); | |
| } | |
| private @Stable static MethodHandle SIMPLE_CONCAT; | |
| private static MethodHandle simpleConcat() { | |
| MethodHandle mh = SIMPLE_CONCAT; | |
| if (mh == null) { | |
| MethodHandle simpleConcat = JLA.stringConcatHelper("simpleConcat", | |
| methodType(String.class, Object.class, Object.class)); | |
| SIMPLE_CONCAT = mh = simpleConcat.rebind(); | |
| } | |
| return mh; | |
| } | |
| private @Stable static MethodHandle NEW_STRING; | |
| private static MethodHandle newString() { | |
| MethodHandle mh = NEW_STRING; | |
| if (mh == null) { | |
| MethodHandle newString = JLA.stringConcatHelper("newString", | |
| methodType(String.class, byte[].class, long.class)); | |
| NEW_STRING = mh = newString.rebind(); | |
| } | |
| return mh; | |
| } | |
| private @Stable static MethodHandle NEW_ARRAY_SUFFIX; | |
| private static MethodHandle newArrayWithSuffix(String suffix) { | |
| MethodHandle mh = NEW_ARRAY_SUFFIX; | |
| if (mh == null) { | |
| MethodHandle newArrayWithSuffix = JLA.stringConcatHelper("newArrayWithSuffix", | |
| methodType(byte[].class, String.class, long.class)); | |
| NEW_ARRAY_SUFFIX = mh = newArrayWithSuffix.rebind(); | |
| } | |
| return MethodHandles.insertArguments(mh, 0, suffix); | |
| } | |
| /** | |
| * Public gateways to public "stringify" methods. These methods have the | |
| * form String apply(T obj), and normally delegate to {@code String.valueOf}, | |
| * depending on argument's type. | |
| */ | |
| private @Stable static MethodHandle OBJECT_STRINGIFIER; | |
| private static MethodHandle objectStringifier() { | |
| MethodHandle mh = OBJECT_STRINGIFIER; | |
| if (mh == null) { | |
| OBJECT_STRINGIFIER = mh = JLA.stringConcatHelper("stringOf", | |
| methodType(String.class, Object.class)); | |
| } | |
| return mh; | |
| } | |
| private @Stable static MethodHandle FLOAT_STRINGIFIER; | |
| private static MethodHandle floatStringifier() { | |
| MethodHandle mh = FLOAT_STRINGIFIER; | |
| if (mh == null) { | |
| FLOAT_STRINGIFIER = mh = stringValueOf(float.class); | |
| } | |
| return mh; | |
| } | |
| private @Stable static MethodHandle DOUBLE_STRINGIFIER; | |
| private static MethodHandle doubleStringifier() { | |
| MethodHandle mh = DOUBLE_STRINGIFIER; | |
| if (mh == null) { | |
| DOUBLE_STRINGIFIER = mh = stringValueOf(double.class); | |
| } | |
| return mh; | |
| } | |
| private @Stable static MethodHandle INT_STRINGIFIER; | |
| private static MethodHandle intStringifier() { | |
| MethodHandle mh = INT_STRINGIFIER; | |
| if (mh == null) { | |
| INT_STRINGIFIER = mh = stringValueOf(int.class); | |
| } | |
| return mh; | |
| } | |
| private @Stable static MethodHandle LONG_STRINGIFIER; | |
| private static MethodHandle longStringifier() { | |
| MethodHandle mh = LONG_STRINGIFIER; | |
| if (mh == null) { | |
| LONG_STRINGIFIER = mh = stringValueOf(long.class); | |
| } | |
| return mh; | |
| } | |
| private @Stable static MethodHandle CHAR_STRINGIFIER; | |
| private static MethodHandle charStringifier() { | |
| MethodHandle mh = CHAR_STRINGIFIER; | |
| if (mh == null) { | |
| CHAR_STRINGIFIER = mh = stringValueOf(char.class); | |
| } | |
| return mh; | |
| } | |
| private @Stable static MethodHandle BOOLEAN_STRINGIFIER; | |
| private static MethodHandle booleanStringifier() { | |
| MethodHandle mh = BOOLEAN_STRINGIFIER; | |
| if (mh == null) { | |
| BOOLEAN_STRINGIFIER = mh = stringValueOf(boolean.class); | |
| } | |
| return mh; | |
| } | |
| private @Stable static MethodHandle NEW_STRINGIFIER; | |
| private static MethodHandle newStringifier() { | |
| MethodHandle mh = NEW_STRINGIFIER; | |
| if (mh == null) { | |
| NEW_STRINGIFIER = mh = JLA.stringConcatHelper("newStringOf", | |
| methodType(String.class, Object.class)); | |
| } | |
| return mh; | |
| } | |
| private static MethodHandle unaryConcat(Class<?> cl) { | |
| if (!cl.isPrimitive()) { | |
| return newStringifier(); | |
| } else if (cl == int.class || cl == short.class || cl == byte.class) { | |
| return intStringifier(); | |
| } else if (cl == long.class) { | |
| return longStringifier(); | |
| } else if (cl == char.class) { | |
| return charStringifier(); | |
| } else if (cl == boolean.class) { | |
| return booleanStringifier(); | |
| } else if (cl == float.class) { | |
| return floatStringifier(); | |
| } else if (cl == double.class) { | |
| return doubleStringifier(); | |
| } else { | |
| throw new InternalError("Unhandled type for unary concatenation: " + cl); | |
| } | |
| } | |
| private static final @Stable MethodHandle[] NO_PREFIX_PREPENDERS = new MethodHandle[TYPE_COUNT]; | |
| private static final @Stable MethodHandle[] PREPENDERS = new MethodHandle[TYPE_COUNT]; | |
| private static final @Stable MethodHandle[] MIXERS = new MethodHandle[TYPE_COUNT]; | |
| private static final long INITIAL_CODER = JLA.stringConcatInitialCoder(); | |
| /** | |
| * Promote integral types to int. | |
| */ | |
| private static Class<?> promoteToIntType(Class<?> t) { | |
| // use int for subword integral types; still need special mixers | |
| // and prependers for char, boolean | |
| return t == byte.class || t == short.class ? int.class : t; | |
| } | |
| /** | |
| * Returns a stringifier for references and floats/doubles only. | |
| * Always returns null for other primitives. | |
| * | |
| * @param t class to stringify | |
| * @return stringifier; null, if not available | |
| */ | |
| private static MethodHandle stringifierFor(Class<?> t) { | |
| if (t == Object.class) { | |
| return objectStringifier(); | |
| } else if (t == float.class) { | |
| return floatStringifier(); | |
| } else if (t == double.class) { | |
| return doubleStringifier(); | |
| } | |
| return null; | |
| } | |
| private static MethodHandle stringValueOf(Class<?> ptype) { | |
| try { | |
| return MethodHandles.publicLookup() | |
| .findStatic(String.class, "valueOf", MethodType.methodType(String.class, ptype)); | |
| } catch (NoSuchMethodException | IllegalAccessException e) { | |
| throw new AssertionError(e); | |
| } | |
| } | |
| private StringConcatFactory() { | |
| // no instantiation | |
| } | |
| /** | |
| * Implement efficient hidden class strategy for String concatenation | |
| * | |
| * <p>This strategy replicates based on the bytecode what StringBuilders are doing: it builds the | |
| * byte[] array on its own and passes that byte[] array to String | |
| * constructor. This strategy requires access to some private APIs in JDK, | |
| * most notably, the private String constructor that accepts byte[] arrays | |
| * without copying. | |
| */ | |
| private static final class InlineHiddenClassStrategy { | |
| // The CLASS_NAME prefix must be the same as used by HeapShared::is_string_concat_klass() | |
| // in the HotSpot code. | |
| static final String CLASS_NAME = "java.lang.String$$StringConcat"; | |
| static final String METHOD_NAME = "concat"; | |
| static final ClassFileDumper DUMPER = | |
| ClassFileDumper.getInstance("java.lang.invoke.StringConcatFactory.dump", "stringConcatClasses"); | |
| static final MethodHandles.Lookup STR_LOOKUP = new MethodHandles.Lookup(String.class); | |
| static final ClassDesc CD_CONCAT = ConstantUtils.binaryNameToDesc(CLASS_NAME); | |
| static final ClassDesc CD_StringConcatHelper = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/StringConcatHelper;"); | |
| static final ClassDesc CD_StringConcatBase = ClassOrInterfaceDescImpl.ofValidated("Ljava/lang/StringConcatHelper$StringConcatBase;"); | |
| static final ClassDesc CD_Array_byte = CD_byte.arrayType(); | |
| static final ClassDesc CD_Array_String = CD_String.arrayType(); | |
| static final MethodTypeDesc MTD_byte_char = MethodTypeDescImpl.ofValidated(CD_byte, CD_char); | |
| static final MethodTypeDesc MTD_byte = MethodTypeDescImpl.ofValidated(CD_byte); | |
| static final MethodTypeDesc MTD_int = MethodTypeDescImpl.ofValidated(CD_int); | |
| static final MethodTypeDesc MTD_int_int_boolean = MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_boolean); | |
| static final MethodTypeDesc MTD_int_int_char = MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_char); | |
| static final MethodTypeDesc MTD_int_int_int = MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_int); | |
| static final MethodTypeDesc MTD_int_int_long = MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_long); | |
| static final MethodTypeDesc MTD_int_int_String = MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_String); | |
| static final MethodTypeDesc MTD_String_float = MethodTypeDescImpl.ofValidated(CD_String, CD_float); | |
| static final MethodTypeDesc MTD_String_double = MethodTypeDescImpl.ofValidated(CD_String, CD_double); | |
| static final MethodTypeDesc MTD_String_Object = MethodTypeDescImpl.ofValidated(CD_String, CD_Object); | |
| static final MethodTypeDesc MTD_INIT = MethodTypeDescImpl.ofValidated(CD_void, CD_Array_String); | |
| static final MethodTypeDesc MTD_NEW_ARRAY_SUFFIX = MethodTypeDescImpl.ofValidated(CD_Array_byte, CD_String, CD_int, CD_byte); | |
| static final MethodTypeDesc MTD_STRING_INIT = MethodTypeDescImpl.ofValidated(CD_void, CD_Array_byte, CD_byte); | |
| static final MethodTypeDesc PREPEND_int = MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_byte, CD_Array_byte, CD_int, CD_String); | |
| static final MethodTypeDesc PREPEND_long = MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_byte, CD_Array_byte, CD_long, CD_String); | |
| static final MethodTypeDesc PREPEND_boolean = MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_byte, CD_Array_byte, CD_boolean, CD_String); | |
| static final MethodTypeDesc PREPEND_char = MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_byte, CD_Array_byte, CD_char, CD_String); | |
| static final MethodTypeDesc PREPEND_String = MethodTypeDescImpl.ofValidated(CD_int, CD_int, CD_byte, CD_Array_byte, CD_String, CD_String); | |
| static final RuntimeVisibleAnnotationsAttribute FORCE_INLINE = RuntimeVisibleAnnotationsAttribute.of(Annotation.of(ClassDesc.ofDescriptor("Ljdk/internal/vm/annotation/ForceInline;"))); | |
| static final MethodType CONSTRUCTOR_METHOD_TYPE = MethodType.methodType(void.class, String[].class); | |
| static final Consumer<CodeBuilder> CONSTRUCTOR_BUILDER = new Consumer<CodeBuilder>() { | |
| @Override | |
| public void accept(CodeBuilder cb) { | |
| /* | |
| * super(constants); | |
| */ | |
| int thisSlot = cb.receiverSlot(), | |
| constantsSlot = cb.parameterSlot(0); | |
| cb.aload(thisSlot) | |
| .aload(constantsSlot) | |
| .invokespecial(CD_StringConcatBase, INIT_NAME, MTD_INIT, false) | |
| .return_(); | |
| } | |
| }; | |
| static final ReferencedKeyMap<MethodType, SoftReference<MethodHandlePair>> CACHE = | |
| ReferencedKeyMap.create(true, | |
| new Supplier<>() { | |
| @Override | |
| public Map<ReferenceKey<MethodType>, SoftReference<MethodHandlePair>> get() { | |
| return new ConcurrentHashMap<>(64); | |
| } | |
| }); | |
| private InlineHiddenClassStrategy() { | |
| // no instantiation | |
| } | |
| private record MethodHandlePair(MethodHandle constructor, MethodHandle concatenator) { }; | |
| /** | |
| * The parameter types are normalized into 7 types: int,long,boolean,char,float,double,Object | |
| */ | |
| private static MethodType erasedArgs(MethodType args) { | |
| int parameterCount = args.parameterCount(); | |
| var paramTypes = new Class<?>[parameterCount]; | |
| boolean changed = false; | |
| for (int i = 0; i < parameterCount; i++) { | |
| Class<?> cl = args.parameterType(i); | |
| // Use int as the logical type for subword integral types | |
| // (byte and short). char and boolean require special | |
| // handling so don't change the logical type of those | |
| if (cl == byte.class || cl == short.class) { | |
| cl = int.class; | |
| changed = true; | |
| } else if (cl != Object.class && !cl.isPrimitive()) { | |
| cl = Object.class; | |
| changed = true; | |
| } | |
| paramTypes[i] = cl; | |
| } | |
| return changed ? MethodType.methodType(args.returnType(), paramTypes, true) : args; | |
| } | |
| /** | |
| * Construct the MethodType of the prepend method, The parameters only support 5 types: | |
| * int/long/char/boolean/String. Not int/long/char/boolean type, use String type<p> | |
| * | |
| * The following is an example of the generated target code: | |
| * <blockquote><pre> | |
| * int prepend(int length, byte coder, byte[] buff, String[] constants | |
| * int arg0, long arg1, boolean arg2, char arg3, String arg5) | |
| * </pre></blockquote> | |
| */ | |
| private static MethodTypeDesc prependArgs(MethodType concatArgs, boolean staticConcat) { | |
| int parameterCount = concatArgs.parameterCount(); | |
| int prefixArgs = staticConcat ? 3 : 4; | |
| var paramTypes = new ClassDesc[parameterCount + prefixArgs]; | |
| paramTypes[0] = CD_int; // length | |
| paramTypes[1] = CD_byte; // coder | |
| paramTypes[2] = CD_Array_byte; // buff | |
| if (!staticConcat) { | |
| paramTypes[3] = CD_Array_String; // constants | |
| } | |
| for (int i = 0; i < parameterCount; i++) { | |
| var cl = concatArgs.parameterType(i); | |
| paramTypes[i + prefixArgs] = needStringOf(cl) ? CD_String : ConstantUtils.classDesc(cl); | |
| } | |
| return MethodTypeDescImpl.ofValidated(CD_int, paramTypes); | |
| } | |
| /** | |
| * Construct the MethodType of the coder method. The first parameter is the initialized coder. | |
| * Only parameter types which can be UTF16 are added. | |
| * Returns null if no such parameter exists or CompactStrings is off. | |
| */ | |
| private static MethodTypeDesc coderArgsIfMaybeUTF16(MethodType concatArgs) { | |
| if (JLA.stringInitCoder() != 0) { | |
| return null; | |
| } | |
| int parameterCount = concatArgs.parameterCount(); | |
| int maybeUTF16Count = 0; | |
| for (int i = 0; i < parameterCount; i++) { | |
| if (maybeUTF16(concatArgs.parameterType(i))) { | |
| maybeUTF16Count++; | |
| } | |
| } | |
| if (maybeUTF16Count == 0) { | |
| return null; | |
| } | |
| var paramTypes = new ClassDesc[maybeUTF16Count + 1]; | |
| paramTypes[0] = CD_int; // init coder | |
| for (int i = 0, paramIndex = 1; i < parameterCount; i++) { | |
| var cl = concatArgs.parameterType(i); | |
| if (maybeUTF16(cl)) { | |
| paramTypes[paramIndex++] = cl == char.class ? CD_char : CD_String; | |
| } | |
| } | |
| return MethodTypeDescImpl.ofValidated(CD_int, paramTypes); | |
| } | |
| /** | |
| * Construct the MethodType of the length method, | |
| * The first parameter is the initialized length | |
| */ | |
| private static MethodTypeDesc lengthArgs(MethodType concatArgs) { | |
| int parameterCount = concatArgs.parameterCount(); | |
| var paramTypes = new ClassDesc[parameterCount + 1]; | |
| paramTypes[0] = CD_int; // init long | |
| for (int i = 0; i < parameterCount; i++) { | |
| var cl = concatArgs.parameterType(i); | |
| paramTypes[i + 1] = needStringOf(cl) ? CD_String : ConstantUtils.classDesc(cl); | |
| } | |
| return MethodTypeDescImpl.ofValidated(CD_int, paramTypes); | |
| } | |
| private static MethodHandle generate(Lookup lookup, MethodType args, String[] constants) throws Exception { | |
| lookup = STR_LOOKUP; | |
| final MethodType concatArgs = erasedArgs(args); | |
| // 1 argument use built-in method | |
| if (args.parameterCount() == 1) { | |
| Object concat1 = JLA.uncheckedStringConcat1(constants); | |
| var handle = lookup.findVirtual(concat1.getClass(), METHOD_NAME, concatArgs); | |
| return handle.bindTo(concat1); | |
| } | |
| boolean forceInline = concatArgs.parameterCount() < FORCE_INLINE_THRESHOLD; | |
| boolean staticConcat = concatArgs.parameterCount() >= CACHE_THRESHOLD; | |
| if (!staticConcat) { | |
| var weakConstructorHandle = CACHE.get(concatArgs); | |
| if (weakConstructorHandle != null) { | |
| MethodHandlePair handlePair = weakConstructorHandle.get(); | |
| if (handlePair != null) { | |
| try { | |
| var instance = handlePair.constructor.invokeBasic((Object)constants); | |
| return handlePair.concatenator.bindTo(instance); | |
| } catch (Throwable e) { | |
| throw new StringConcatException("Exception while utilizing the hidden class", e); | |
| } | |
| } | |
| } | |
| } | |
| MethodTypeDesc lengthArgs = lengthArgs(concatArgs), | |
| coderArgs = coderArgsIfMaybeUTF16(concatArgs), | |
| prependArgs = prependArgs(concatArgs, staticConcat); | |
| byte[] classBytes = ClassFile.of().build(CD_CONCAT, | |
| new Consumer<ClassBuilder>() { | |
| @Override | |
| public void accept(ClassBuilder clb) { | |
| if (staticConcat) { | |
| clb.withSuperclass(CD_Object) | |
| .withFlags(ACC_ABSTRACT | ACC_SUPER | ACC_SYNTHETIC); | |
| } else { | |
| clb.withSuperclass(CD_StringConcatBase) | |
| .withFlags(ACC_FINAL | ACC_SUPER | ACC_SYNTHETIC) | |
| .withMethodBody(INIT_NAME, MTD_INIT, 0, CONSTRUCTOR_BUILDER); | |
| } | |
| clb.withMethod("length", | |
| lengthArgs, | |
| ACC_STATIC | ACC_PRIVATE, | |
| new Consumer<MethodBuilder>() { | |
| public void accept(MethodBuilder mb) { | |
| if (forceInline) { | |
| mb.with(FORCE_INLINE); | |
| } | |
| mb.withCode(generateLengthMethod(lengthArgs)); | |
| } | |
| }) | |
| .withMethod("prepend", | |
| prependArgs, | |
| ACC_STATIC | ACC_PRIVATE, | |
| new Consumer<MethodBuilder>() { | |
| public void accept(MethodBuilder mb) { | |
| if (forceInline) { | |
| mb.with(FORCE_INLINE); | |
| } | |
| mb.withCode(generatePrependMethod(prependArgs, staticConcat, constants)); | |
| } | |
| }) | |
| .withMethod(METHOD_NAME, | |
| ConstantUtils.methodTypeDesc(concatArgs), | |
| staticConcat ? ACC_STATIC | ACC_FINAL : ACC_FINAL, | |
| new Consumer<MethodBuilder>() { | |
| public void accept(MethodBuilder mb) { | |
| if (forceInline) { | |
| mb.with(FORCE_INLINE); | |
| } | |
| mb.withCode(generateConcatMethod( | |
| staticConcat, | |
| constants, | |
| CD_CONCAT, | |
| concatArgs, | |
| lengthArgs, | |
| coderArgs, | |
| prependArgs)); | |
| } | |
| }); | |
| if (coderArgs != null) { | |
| clb.withMethod("coder", | |
| coderArgs, | |
| ACC_STATIC | ACC_PRIVATE, | |
| new Consumer<MethodBuilder>() { | |
| public void accept(MethodBuilder mb) { | |
| if (forceInline) { | |
| mb.with(FORCE_INLINE); | |
| } | |
| mb.withCode(generateCoderMethod(coderArgs)); | |
| } | |
| }); | |
| } | |
| }}); | |
| try { | |
| var hiddenClass = lookup.makeHiddenClassDefiner(CLASS_NAME, classBytes, DUMPER) | |
| .defineClass(true, null); | |
| if (staticConcat) { | |
| return lookup.findStatic(hiddenClass, METHOD_NAME, concatArgs); | |
| } | |
| var constructor = lookup.findConstructor(hiddenClass, CONSTRUCTOR_METHOD_TYPE); | |
| var concatenator = lookup.findVirtual(hiddenClass, METHOD_NAME, concatArgs); | |
| CACHE.put(concatArgs, new SoftReference<>(new MethodHandlePair(constructor, concatenator))); | |
| var instance = constructor.invokeBasic((Object)constants); | |
| return concatenator.bindTo(instance); | |
| } catch (Throwable e) { | |
| throw new StringConcatException("Exception while spinning the class", e); | |
| } | |
| } | |
| /** | |
| * Generate InlineCopy-based code. <p> | |
| * | |
| * The following is an example of the generated target code: | |
| * | |
| * <blockquote><pre> | |
| * import static java.lang.StringConcatHelper.newArrayWithSuffix; | |
| * import static java.lang.StringConcatHelper.prepend; | |
| * import static java.lang.StringConcatHelper.stringCoder; | |
| * import static java.lang.StringConcatHelper.stringSize; | |
| * | |
| * class StringConcat extends java.lang.StringConcatHelper.StringConcatBase { | |
| * // super class defines | |
| * // String[] constants; | |
| * // int length; | |
| * // byte coder; | |
| * | |
| * StringConcat(String[] constants) { | |
| * super(constants); | |
| * } | |
| * | |
| * String concat(int arg0, long arg1, boolean arg2, char arg3, String arg4, | |
| * float arg5, double arg6, Object arg7 | |
| * ) { | |
| * // Types other than byte/short/int/long/boolean/String require a local variable to store | |
| * String str4 = stringOf(arg4); | |
| * String str5 = stringOf(arg5); | |
| * String str6 = stringOf(arg6); | |
| * String str7 = stringOf(arg7); | |
| * | |
| * int coder = coder(this.coder, arg0, arg1, arg2, arg3, str4, str5, str6, str7); | |
| * int length = length(this.length, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); | |
| * String[] constants = this.constants; | |
| * byte[] buf = newArrayWithSuffix(constants[paramCount], length. coder); | |
| * | |
| * prepend(length, coder, buf, constants, arg0, arg1, arg2, arg3, str4, str5, str6, str7); | |
| * | |
| * return new String(buf, coder); | |
| * } | |
| * | |
| * static int length(int length, int arg0, long arg1, boolean arg2, char arg3, | |
| * String arg4, String arg5, String arg6, String arg7) { | |
| * return stringSize(stringSize(stringSize(stringSize(stringSize(stringSize(stringSize(stringSize( | |
| * length, arg0), arg1), arg2), arg3), arg4), arg5), arg6), arg7); | |
| * } | |
| * | |
| * static int cocder(int coder, char arg3, String str4, String str5, String str6, String str7) { | |
| * return coder | stringCoder(arg3) | str4.coder() | str5.coder() | str6.coder() | str7.coder(); | |
| * } | |
| * | |
| * static int prepend(int length, int coder, byte[] buf, String[] constants, | |
| * int arg0, long arg1, boolean arg2, char arg3, | |
| * String str4, String str5, String str6, String str7) { | |
| * // StringConcatHelper.prepend | |
| * return prepend(prepend(prepend(prepend( | |
| * prepend(apppend(prepend(prepend(length, | |
| * buf, str7, constant[7]), buf, str6, constant[6]), | |
| * buf, str5, constant[5]), buf, str4, constant[4]), | |
| * buf, arg3, constant[3]), buf, arg2, constant[2]), | |
| * buf, arg1, constant[1]), buf, arg0, constant[0]); | |
| * } | |
| * } | |
| * </pre></blockquote> | |
| */ | |
| private static Consumer<CodeBuilder> generateConcatMethod( | |
| boolean staticConcat, | |
| String[] constants, | |
| ClassDesc concatClass, | |
| MethodType concatArgs, | |
| MethodTypeDesc lengthArgs, | |
| MethodTypeDesc coderArgs, | |
| MethodTypeDesc prependArgs | |
| ) { | |
| return new Consumer<CodeBuilder>() { | |
| @Override | |
| public void accept(CodeBuilder cb) { | |
| // Compute parameter variable slots | |
| int paramCount = concatArgs.parameterCount(), | |
| thisSlot = staticConcat ? 0 : cb.receiverSlot(), | |
| lengthSlot = cb.allocateLocal(TypeKind.INT), | |
| coderSlot = cb.allocateLocal(TypeKind.BYTE), | |
| bufSlot = cb.allocateLocal(TypeKind.REFERENCE), | |
| constantsSlot = cb.allocateLocal(TypeKind.REFERENCE), | |
| suffixSlot = cb.allocateLocal(TypeKind.REFERENCE); | |
| /* | |
| * Types other than int/long/char/boolean require local variables to store the result of stringOf. | |
| * | |
| * stringSlots stores the slots of parameters relative to local variables | |
| * | |
| * str0 = stringOf(arg0); | |
| * str1 = stringOf(arg1); | |
| * ... | |
| * strN = toString(argN); | |
| */ | |
| int[] stringSlots = new int[paramCount]; | |
| for (int i = 0; i < paramCount; i++) { | |
| var cl = concatArgs.parameterType(i); | |
| if (needStringOf(cl)) { | |
| MethodTypeDesc methodTypeDesc; | |
| if (cl == float.class) { | |
| methodTypeDesc = MTD_String_float; | |
| } else if (cl == double.class) { | |
| methodTypeDesc = MTD_String_double; | |
| } else { | |
| methodTypeDesc = MTD_String_Object; | |
| } | |
| stringSlots[i] = cb.allocateLocal(TypeKind.REFERENCE); | |
| cb.loadLocal(TypeKind.from(cl), cb.parameterSlot(i)) | |
| .invokestatic(CD_StringConcatHelper, "stringOf", methodTypeDesc) | |
| .astore(stringSlots[i]); | |
| } | |
| } | |
| int coder = JLA.stringInitCoder(), | |
| length = 0; | |
| if (staticConcat) { | |
| for (var constant : constants) { | |
| coder |= JLA.stringCoder(constant); | |
| length += constant.length(); | |
| } | |
| } | |
| /* | |
| * coder = coder(this.coder, arg0, arg1, ... argN); | |
| */ | |
| if (staticConcat) { | |
| // coder can only be 0 or 1 | |
| if (coder == 0) { | |
| cb.iconst_0(); | |
| } else { | |
| cb.iconst_1(); | |
| } | |
| } else { | |
| cb.aload(thisSlot) | |
| .getfield(concatClass, "coder", CD_byte); | |
| } | |
| if (coderArgs != null) { | |
| for (int i = 0; i < paramCount; i++) { | |
| var cl = concatArgs.parameterType(i); | |
| if (maybeUTF16(cl)) { | |
| if (cl == char.class) { | |
| cb.loadLocal(TypeKind.CHAR, cb.parameterSlot(i)); | |
| } else { | |
| cb.aload(stringSlots[i]); | |
| } | |
| } | |
| } | |
| cb.invokestatic(concatClass, "coder", coderArgs); | |
| } | |
| cb.istore(coderSlot); | |
| /* | |
| * length = length(this.length, arg0, arg1, ..., argN); | |
| */ | |
| if (staticConcat) { | |
| cb.loadConstant(length); | |
| } else { | |
| cb.aload(thisSlot) | |
| .getfield(concatClass, "length", CD_int); | |
| } | |
| for (int i = 0; i < paramCount; i++) { | |
| var cl = concatArgs.parameterType(i); | |
| int paramSlot = cb.parameterSlot(i); | |
| if (needStringOf(cl)) { | |
| paramSlot = stringSlots[i]; | |
| cl = String.class; | |
| } | |
| cb.loadLocal(TypeKind.from(cl), paramSlot); | |
| } | |
| cb.invokestatic(concatClass, "length", lengthArgs); | |
| /* | |
| * String[] constants = this.constants; | |
| * suffix = constants[paramCount]; | |
| * length -= suffix.length(); | |
| */ | |
| if (staticConcat) { | |
| cb.loadConstant(constants[paramCount].length()) | |
| .isub() | |
| .istore(lengthSlot); | |
| } else { | |
| cb.aload(thisSlot) | |
| .getfield(concatClass, "constants", CD_Array_String) | |
| .dup() | |
| .astore(constantsSlot) | |
| .loadConstant(paramCount) | |
| .aaload() | |
| .dup() | |
| .astore(suffixSlot) | |
| .invokevirtual(CD_String, "length", MTD_int) | |
| .isub() | |
| .istore(lengthSlot); | |
| } | |
| /* | |
| * Allocate buffer : | |
| * | |
| * buf = newArrayWithSuffix(suffix, length, coder) | |
| */ | |
| if (staticConcat) { | |
| cb.loadConstant(constants[paramCount]); | |
| } else { | |
| cb.aload(suffixSlot); | |
| } | |
| cb.iload(lengthSlot) | |
| .iload(coderSlot) | |
| .invokestatic(CD_StringConcatHelper, "newArrayWithSuffix", MTD_NEW_ARRAY_SUFFIX) | |
| .astore(bufSlot); | |
| /* | |
| * prepend(length, coder, buf, constants, ar0, ar1, ..., argN); | |
| */ | |
| cb.iload(lengthSlot) | |
| .iload(coderSlot) | |
| .aload(bufSlot); | |
| if (!staticConcat) { | |
| cb.aload(constantsSlot); | |
| } | |
| for (int i = 0; i < paramCount; i++) { | |
| var cl = concatArgs.parameterType(i); | |
| int paramSlot = cb.parameterSlot(i); | |
| var kind = TypeKind.from(cl); | |
| if (needStringOf(cl)) { | |
| paramSlot = stringSlots[i]; | |
| kind = TypeKind.REFERENCE; | |
| } | |
| cb.loadLocal(kind, paramSlot); | |
| } | |
| cb.invokestatic(concatClass, "prepend", prependArgs); | |
| // return new String(buf, coder); | |
| cb.new_(CD_String) | |
| .dup() | |
| .aload(bufSlot) | |
| .iload(coderSlot) | |
| .invokespecial(CD_String, INIT_NAME, MTD_STRING_INIT) | |
| .areturn(); | |
| } | |
| }; | |
| } | |
| /** | |
| * Generate length method. <p> | |
| * | |
| * The following is an example of the generated target code: | |
| * | |
| * <blockquote><pre> | |
| * import static java.lang.StringConcatHelper.stringSize; | |
| * | |
| * static int length(int length, int arg0, long arg1, boolean arg2, char arg3, | |
| * String arg4, String arg5, String arg6, String arg7) { | |
| * return stringSize(stringSize(stringSize(length, arg0), arg1), ..., arg7); | |
| * } | |
| * </pre></blockquote> | |
| */ | |
| private static Consumer<CodeBuilder> generateLengthMethod(MethodTypeDesc lengthArgs) { | |
| return new Consumer<CodeBuilder>() { | |
| @Override | |
| public void accept(CodeBuilder cb) { | |
| int lengthSlot = cb.parameterSlot(0); | |
| cb.iload(lengthSlot); | |
| for (int i = 1; i < lengthArgs.parameterCount(); i++) { | |
| var cl = lengthArgs.parameterType(i); | |
| MethodTypeDesc methodTypeDesc; | |
| if (cl == CD_char) { | |
| methodTypeDesc = MTD_int_int_char; | |
| } else if (cl == CD_int) { | |
| methodTypeDesc = MTD_int_int_int; | |
| } else if (cl == CD_long) { | |
| methodTypeDesc = MTD_int_int_long; | |
| } else if (cl == CD_boolean) { | |
| methodTypeDesc = MTD_int_int_boolean; | |
| } else { | |
| methodTypeDesc = MTD_int_int_String; | |
| } | |
| cb.loadLocal(TypeKind.from(cl), cb.parameterSlot(i)) | |
| .invokestatic(CD_StringConcatHelper, "stringSize", methodTypeDesc); | |
| } | |
| cb.ireturn(); | |
| } | |
| }; | |
| } | |
| /** | |
| * Generate coder method. <p> | |
| * | |
| * The following is an example of the generated target code: | |
| * | |
| * <blockquote><pre> | |
| * import static java.lang.StringConcatHelper.stringCoder; | |
| * | |
| * static int cocder(int coder, char arg3, String str4, String str5, String str6, String str7) { | |
| * return coder | stringCoder(arg3) | str4.coder() | str5.coder() | str6.coder() | str7.coder(); | |
| * } | |
| * </pre></blockquote> | |
| */ | |
| private static Consumer<CodeBuilder> generateCoderMethod(MethodTypeDesc coderArgs) { | |
| return new Consumer<CodeBuilder>() { | |
| @Override | |
| public void accept(CodeBuilder cb) { | |
| /* | |
| * return coder | stringCoder(argN) | ... | arg1.coder() | arg0.coder(); | |
| */ | |
| int coderSlot = cb.parameterSlot(0); | |
| cb.iload(coderSlot); | |
| for (int i = 1; i < coderArgs.parameterCount(); i++) { | |
| var cl = coderArgs.parameterType(i); | |
| cb.loadLocal(TypeKind.from(cl), cb.parameterSlot(i)); | |
| if (cl == CD_char) { | |
| cb.invokestatic(CD_StringConcatHelper, "stringCoder", MTD_byte_char); | |
| } else { | |
| cb.invokevirtual(CD_String, "coder", MTD_byte); | |
| } | |
| cb.ior(); | |
| } | |
| cb.ireturn(); | |
| } | |
| }; | |
| } | |
| /** | |
| * Generate prepend method. <p> | |
| * | |
| * The following is an example of the generated target code: | |
| * | |
| * <blockquote><pre> | |
| * import static java.lang.StringConcatHelper.prepend; | |
| * | |
| * static int prepend(int length, int coder, byte[] buf, String[] constants, | |
| * int arg0, long arg1, boolean arg2, char arg3, | |
| * String str4, String str5, String str6, String str7) { | |
| * | |
| * return prepend(prepend(prepend(prepend( | |
| * prepend(prepend(prepend(prepend(length, | |
| * buf, str7, constant[7]), buf, str6, constant[6]), | |
| * buf, str5, constant[5]), buf, str4, constant[4]), | |
| * buf, arg3, constant[3]), buf, arg2, constant[2]), | |
| * buf, arg1, constant[1]), buf, arg0, constant[0]); | |
| * } | |
| * </pre></blockquote> | |
| */ | |
| private static Consumer<CodeBuilder> generatePrependMethod( | |
| MethodTypeDesc prependArgs, | |
| boolean staticConcat, String[] constants | |
| ) { | |
| return new Consumer<CodeBuilder>() { | |
| @Override | |
| public void accept(CodeBuilder cb) { | |
| // Compute parameter variable slots | |
| int lengthSlot = cb.parameterSlot(0), | |
| coderSlot = cb.parameterSlot(1), | |
| bufSlot = cb.parameterSlot(2), | |
| constantsSlot = cb.parameterSlot(3); | |
| /* | |
| * // StringConcatHelper.prepend | |
| * return prepend(prepend(prepend(prepend( | |
| * prepend(apppend(prepend(prepend(length, | |
| * buf, str7, constant[7]), buf, str6, constant[6]), | |
| * buf, str5, constant[5]), buf, arg4, constant[4]), | |
| * buf, arg3, constant[3]), buf, arg2, constant[2]), | |
| * buf, arg1, constant[1]), buf, arg0, constant[0]); | |
| */ | |
| cb.iload(lengthSlot); | |
| for (int i = prependArgs.parameterCount() - 1, end = staticConcat ? 3 : 4; i >= end; i--) { | |
| var cl = prependArgs.parameterType(i); | |
| var kind = TypeKind.from(cl); | |
| // There are only 5 types of parameters: int, long, boolean, char, String | |
| MethodTypeDesc methodTypeDesc; | |
| if (cl == CD_int) { | |
| methodTypeDesc = PREPEND_int; | |
| } else if (cl == CD_long) { | |
| methodTypeDesc = PREPEND_long; | |
| } else if (cl == CD_boolean) { | |
| methodTypeDesc = PREPEND_boolean; | |
| } else if (cl == CD_char) { | |
| methodTypeDesc = PREPEND_char; | |
| } else { | |
| kind = TypeKind.REFERENCE; | |
| methodTypeDesc = PREPEND_String; | |
| } | |
| cb.iload(coderSlot) | |
| .aload(bufSlot) | |
| .loadLocal(kind, cb.parameterSlot(i)); | |
| if (staticConcat) { | |
| cb.loadConstant(constants[i - 3]); | |
| } else { | |
| cb.aload(constantsSlot) | |
| .loadConstant(i - 4) | |
| .aaload(); | |
| } | |
| cb.invokestatic(CD_StringConcatHelper, "prepend", methodTypeDesc); | |
| } | |
| cb.ireturn(); | |
| } | |
| }; | |
| } | |
| static boolean needStringOf(Class<?> cl) { | |
| return cl != int.class && cl != long.class && cl != boolean.class && cl != char.class; | |
| } | |
| static boolean maybeUTF16(Class<?> cl) { | |
| return cl == char.class || !cl.isPrimitive(); | |
| } | |
| } | |
| } |