/* * Copyright (c) 2015, 2020, Oracle and/or its affiliates. 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.misc.Unsafe; import jdk.internal.misc.VM; import jdk.internal.org.objectweb.asm.ClassWriter; import jdk.internal.org.objectweb.asm.Label; import jdk.internal.org.objectweb.asm.MethodVisitor; import jdk.internal.org.objectweb.asm.Opcodes; import jdk.internal.vm.annotation.Stable; import sun.invoke.util.Wrapper; import java.lang.invoke.MethodHandles.Lookup; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.Function; import static java.lang.invoke.MethodHandles.lookup; import static java.lang.invoke.MethodType.methodType; import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*; import static jdk.internal.org.objectweb.asm.Opcodes.*; /** *
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 bootstrap methods for {@code * invokedynamic} call sites, to support the string concatenation * feature of the Java Programming Language. * *
Indirect access to the behavior specified by the provided {@code * MethodHandle} proceeds in order through two phases: * *
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 *
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 {
/**
* 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;
/**
* Concatenation strategy to use. See {@link Strategy} for possible options.
* This option is controllable with -Djava.lang.invoke.stringConcat JDK option.
*
* Defaults to MH_INLINE_SIZED_EXACT if not set.
*/
private static final Strategy STRATEGY;
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
private enum Strategy {
/**
* Bytecode generator, calling into {@link java.lang.StringBuilder}.
*/
BC_SB,
/**
* Bytecode generator, calling into {@link java.lang.StringBuilder};
* but trying to estimate the required storage.
*/
BC_SB_SIZED,
/**
* Bytecode generator, calling into {@link java.lang.StringBuilder};
* but computing the required storage exactly.
*/
BC_SB_SIZED_EXACT,
/**
* MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}.
* This strategy also tries to estimate the required storage.
*/
MH_SB_SIZED,
/**
* MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}.
* This strategy also estimate the required storage exactly.
*/
MH_SB_SIZED_EXACT,
/**
* MethodHandle-based generator, that constructs its own byte[] array from
* the arguments. It computes the required storage exactly.
*/
MH_INLINE_SIZED_EXACT
}
/**
* Enables debugging: this may print debugging messages, perform additional (non-neutral for performance)
* checks, etc.
*/
private static final boolean DEBUG;
static {
final String strategy =
VM.getSavedProperty("java.lang.invoke.stringConcat");
STRATEGY = (strategy == null) ? null : Strategy.valueOf(strategy);
if (STRATEGY == null || STRATEGY == Strategy.MH_INLINE_SIZED_EXACT) {
// Force initialization of default strategy:
Unsafe.getUnsafe().ensureClassInitialized(MethodHandleInlineCopyStrategy.class);
}
DEBUG = Boolean.parseBoolean(
VM.getSavedProperty("java.lang.invoke.stringConcat.debug"));
}
/**
* Cache key is a composite of:
* - class name, that lets to disambiguate stubs, to avoid excess sharing
* - method type, describing the dynamic arguments for concatenation
* - concat recipe, describing the constants and concat shape
*/
private static final class Key {
final String className;
final MethodType mt;
final Recipe recipe;
public Key(String className, MethodType mt, Recipe recipe) {
this.className = className;
this.mt = mt;
this.recipe = recipe;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Key key = (Key) o;
if (!className.equals(key.className)) return false;
if (!mt.equals(key.mt)) return false;
if (!recipe.equals(key.recipe)) return false;
return true;
}
@Override
public int hashCode() {
int result = className.hashCode();
result = 31 * result + mt.hashCode();
result = 31 * result + recipe.hashCode();
return result;
}
}
/**
* Parses the recipe string, and produces a traversable collection of
* {@link java.lang.invoke.StringConcatFactory.RecipeElement}-s for generator
* strategies. Notably, this class parses out the constants from the recipe
* and from other static arguments.
*/
private static final class Recipe {
private final List 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:
* Assume the linkage arguments are as follows:
*
* Then the following linkage invariants must hold:
*
* 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:
* The concatenation recipe 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:
*
* Assume the linkage arguments are as follows:
*
* Then the following linkage invariants must hold:
*
* This strategy operates in three modes, gated by {@link Mode}.
*
* {@link Strategy#BC_SB}: "bytecode StringBuilder".
*
* This strategy spins up the bytecode that has the same StringBuilder
* chain javac would otherwise emit. This strategy uses only the public API,
* and comes as the baseline for the current JDK behavior. On other words,
* this strategy moves the javac generated bytecode to runtime. The
* generated bytecode is loaded via Lookup::defineClass, but with
* the caller class coming from the BSM -- in other words, the protection
* guarantees are inherited from the method where invokedynamic was
* originally called. This means, among other things, that the bytecode is
* verified for all non-JDK uses.
*
* {@link Strategy#BC_SB_SIZED}: "bytecode StringBuilder, but
* sized".
*
* This strategy acts similarly to {@link Strategy#BC_SB}, but it also
* tries to guess the capacity required for StringBuilder to accept all
* arguments without resizing. This strategy only makes an educated guess:
* it only guesses the space required for known types (e.g. primitives and
* Strings), but does not otherwise convert arguments. Therefore, the
* capacity estimate may be wrong, and StringBuilder may have to
* transparently resize or trim when doing the actual concatenation. While
* this does not constitute a correctness issue (in the end, that what BC_SB
* has to do anyway), this does pose a potential performance problem.
*
* {@link Strategy#BC_SB_SIZED_EXACT}: "bytecode StringBuilder, but
* sized exactly".
*
* This strategy improves on @link Strategy#BC_SB_SIZED}, by first
* converting all arguments to String in order to get the exact capacity
* StringBuilder should have. The conversion is done via the public
* String.valueOf and/or Object.toString methods, and does not touch any
* private String API.
*/
private static final class BytecodeStringBuilderStrategy {
static final int CLASSFILE_VERSION = 52;
static final String METHOD_NAME = "concat";
private static final ConcurrentMap This strategy operates in two modes, gated by {@link Mode}.
*
* {@link Strategy#MH_SB_SIZED}: "MethodHandles StringBuilder,
* sized".
*
* This strategy avoids spinning up the bytecode by building the
* computation on MethodHandle combinators. The computation is built with
* public MethodHandle APIs, resolved from a public Lookup sequence, and
* ends up calling the public StringBuilder API. Therefore, this strategy
* does not use any private API at all since everything is handled under
* cover by java.lang.invoke APIs.
*
* {@link Strategy#MH_SB_SIZED_EXACT}: "MethodHandles StringBuilder,
* sized exactly".
*
* This strategy improves on @link Strategy#MH_SB_SIZED}, by first
* converting all arguments to String in order to get the exact capacity
* StringBuilder should have. The conversion is done via the public
* String.valueOf and/or Object.toString methods, and does not touch any
* private String API.
*/
private static final class MethodHandleStringBuilderStrategy {
private MethodHandleStringBuilderStrategy() {
// no instantiation
}
private static MethodHandle generate(MethodType mt, Recipe recipe, Mode mode) throws Exception {
int pc = mt.parameterCount();
Class>[] ptypes = mt.parameterArray();
MethodHandle[] filters = new MethodHandle[ptypes.length];
for (int i = 0; i < ptypes.length; i++) {
MethodHandle filter;
switch (mode) {
case SIZED:
// In sized mode, we convert all references and floats/doubles
// to String: there is no specialization for different
// classes in StringBuilder API, and it will convert to
// String internally anyhow.
filter = Stringifiers.forMost(ptypes[i]);
break;
case SIZED_EXACT:
// In exact mode, we convert everything to String:
// this helps to compute the storage exactly.
filter = Stringifiers.forAny(ptypes[i]);
break;
default:
throw new StringConcatException("Not supported");
}
if (filter != null) {
filters[i] = filter;
ptypes[i] = filter.type().returnType();
}
}
MethodHandle[] lengthers = new MethodHandle[pc];
// Figure out lengths: constants' lengths can be deduced on the spot.
// All reference arguments were filtered to String in the combinators below, so we can
// call the usual String.length(). Primitive values string sizes can be estimated.
int initial = 0;
for (RecipeElement el : recipe.getElements()) {
switch (el.getTag()) {
case TAG_CONST:
initial += el.getValue().length();
break;
case TAG_ARG:
final int i = el.getArgPos();
Class> type = ptypes[i];
if (type.isPrimitive()) {
MethodHandle est = MethodHandles.constant(int.class, estimateSize(type));
est = MethodHandles.dropArguments(est, 0, type);
lengthers[i] = est;
} else {
lengthers[i] = STRING_LENGTH;
}
break;
default:
throw new StringConcatException("Unhandled tag: " + el.getTag());
}
}
// Create (StringBuilder, {@link Strategy#MH_INLINE_SIZED_EXACT}: "MethodHandles inline,
* sized exactly".
*
* 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 read-only Integer/Long.stringSize methods that measure
* the character length of the integers, and the private String constructor
* that accepts byte[] arrays without copying. While this strategy assumes a
* particular implementation details for String, this opens the door for
* building a very optimal concatenation sequence. This is the only strategy
* that requires porting if there are private JDK changes occur.
*/
private static final class MethodHandleInlineCopyStrategy {
private MethodHandleInlineCopyStrategy() {
// no instantiation
}
static MethodHandle generate(MethodType mt, Recipe recipe) throws Throwable {
// Fast-path two-argument Object + Object concatenations
if (recipe.getElements().size() == 2) {
// Two object arguments
if (mt.parameterCount() == 2 &&
!mt.parameterType(0).isPrimitive() &&
!mt.parameterType(1).isPrimitive() &&
recipe.getElements().get(0).getTag() == TAG_ARG &&
recipe.getElements().get(1).getTag() == TAG_ARG) {
return simpleConcat();
} else if (mt.parameterCount() == 1 &&
!mt.parameterType(0).isPrimitive()) {
// One Object argument, one constant
MethodHandle mh = simpleConcat();
if (recipe.getElements().get(0).getTag() == TAG_CONST &&
recipe.getElements().get(1).getTag() == TAG_ARG) {
// First recipe element is a constant
return MethodHandles.insertArguments(mh, 0,
recipe.getElements().get(0).getValue());
} else if (recipe.getElements().get(1).getTag() == TAG_CONST &&
recipe.getElements().get(0).getTag() == TAG_ARG) {
// Second recipe element is a constant
return MethodHandles.insertArguments(mh, 1,
recipe.getElements().get(1).getValue());
}
}
// 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.parameterArray();
MethodHandle[] filters = null;
for (int i = 0; i < ptypes.length; i++) {
MethodHandle filter = Stringifiers.forMost(ptypes[i]);
if (filter != null) {
if (filters == null) {
filters = new MethodHandle[ptypes.length];
}
filters[i] = filter;
ptypes[i] = filter.type().returnType();
}
}
// Start building the combinator tree. The tree "starts" with (
*
*
*
*
*
*
*
*
* @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 {
if (DEBUG) {
System.out.println("StringConcatFactory " + STRATEGY + " is here for " + concatType);
}
return doStringConcat(lookup, name, concatType, true, null);
}
/**
* 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 bootstrap method for {@code
* invokedynamic} call sites, to support the string concatenation
* feature of the Java Programming Language.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
* @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 {
if (DEBUG) {
System.out.println("StringConcatFactory " + STRATEGY + " is here for " + concatType + ", {" + recipe + "}, " + Arrays.toString(constants));
}
return doStringConcat(lookup, name, concatType, false, recipe, constants);
}
private static CallSite doStringConcat(MethodHandles.Lookup lookup,
String name,
MethodType concatType,
boolean generateRecipe,
String recipe,
Object... constants) throws StringConcatException {
Objects.requireNonNull(lookup, "Lookup is null");
Objects.requireNonNull(name, "Name 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());
}
int cCount = 0;
int oCount = 0;
if (generateRecipe) {
// Mock the recipe to reuse the concat generator code
char[] value = new char[concatType.parameterCount()];
Arrays.fill(value, TAG_ARG);
recipe = new String(value);
oCount = concatType.parameterCount();
} else {
Objects.requireNonNull(recipe, "Recipe is null");
for (int i = 0; i < recipe.length(); i++) {
char c = recipe.charAt(i);
if (c == TAG_CONST) cCount++;
if (c == TAG_ARG) oCount++;
}
}
if (oCount != concatType.parameterCount()) {
throw new StringConcatException(
"Mismatched number of concat arguments: recipe wants " +
oCount +
" arguments, but signature provides " +
concatType.parameterCount());
}
if (cCount != constants.length) {
throw new StringConcatException(
"Mismatched number of concat constants: recipe wants " +
cCount +
" constants, but only " +
constants.length +
" are passed");
}
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);
}
MethodType mt = adaptType(concatType);
Recipe rec = new Recipe(recipe, constants);
MethodHandle mh = generate(lookup, mt, rec);
return new ConstantCallSite(mh.asType(concatType));
}
/**
* Adapt method type to an API we are going to use.
*
* This strips the concrete classes from the signatures, thus preventing
* class leakage when we cache the concatenation stubs.
*
* @param args actual argument types
* @return argument types the strategy is going to use
*/
private static MethodType adaptType(MethodType args) {
Class>[] ptypes = null;
for (int i = 0; i < args.parameterCount(); i++) {
Class> ptype = args.parameterType(i);
if (!ptype.isPrimitive() &&
ptype != String.class &&
ptype != Object.class) { // truncate to Object
if (ptypes == null) {
ptypes = args.parameterArray();
}
ptypes[i] = Object.class;
}
// else other primitives or String or Object (unchanged)
}
return (ptypes != null)
? MethodType.methodType(args.returnType(), ptypes)
: args;
}
private static MethodHandle generate(Lookup lookup, MethodType mt, Recipe recipe) throws StringConcatException {
try {
if (STRATEGY == null) {
return MethodHandleInlineCopyStrategy.generate(mt, recipe);
}
switch (STRATEGY) {
case BC_SB:
return BytecodeStringBuilderStrategy.generate(lookup, mt, recipe, Mode.DEFAULT);
case BC_SB_SIZED:
return BytecodeStringBuilderStrategy.generate(lookup, mt, recipe, Mode.SIZED);
case BC_SB_SIZED_EXACT:
return BytecodeStringBuilderStrategy.generate(lookup, mt, recipe, Mode.SIZED_EXACT);
case MH_SB_SIZED:
return MethodHandleStringBuilderStrategy.generate(mt, recipe, Mode.SIZED);
case MH_SB_SIZED_EXACT:
return MethodHandleStringBuilderStrategy.generate(mt, recipe, Mode.SIZED_EXACT);
case MH_INLINE_SIZED_EXACT:
return MethodHandleInlineCopyStrategy.generate(mt, recipe);
default:
throw new StringConcatException("Concatenation strategy " + STRATEGY + " is not implemented");
}
} catch (Error | StringConcatException e) {
// Pass through any error or existing StringConcatException
throw e;
} catch (Throwable t) {
throw new StringConcatException("Generator failed", t);
}
}
private enum Mode {
DEFAULT(false, false),
SIZED(true, false),
SIZED_EXACT(true, true);
private final boolean sized;
private final boolean exact;
Mode(boolean sized, boolean exact) {
this.sized = sized;
this.exact = exact;
}
boolean isSized() {
return sized;
}
boolean isExact() {
return exact;
}
}
/**
* Bytecode StringBuilder strategy.
*
*