/* * 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.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 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.
*/
private static Strategy STRATEGY;
/**
* Default strategy to use for concatenation.
*/
private static final Strategy DEFAULT_STRATEGY = Strategy.MH_INLINE_SIZED_EXACT;
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;
/**
* Enables caching of strategy stubs. This may improve the linkage time by reusing the generated
* code, at the expense of contaminating the profiles.
*/
private static final boolean CACHE_ENABLE;
private static final ConcurrentMap 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 BytecodeStringBuilderStrategy() {
// no instantiation
}
private static MethodHandle generate(Lookup lookup, String className, MethodType args, Recipe recipe, Mode mode) throws Exception {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
cw.visit(CLASSFILE_VERSION,
ACC_SUPER + ACC_PUBLIC + ACC_FINAL + ACC_SYNTHETIC,
className,
null,
"java/lang/Object",
null
);
MethodVisitor mv = cw.visitMethod(
ACC_PUBLIC + ACC_STATIC + ACC_FINAL,
METHOD_NAME,
args.toMethodDescriptorString(),
null,
null);
// use of @ForceInline no longer has any effect
mv.visitAnnotation("Ljdk/internal/vm/annotation/ForceInline;", true);
mv.visitCode();
Class>[] arr = args.parameterArray();
boolean[] guaranteedNonNull = new boolean[arr.length];
if (mode.isExact()) {
/*
In exact mode, we need to convert all arguments to their String representations,
as this allows to compute their String sizes exactly. We cannot use private
methods for primitives in here, therefore we need to convert those as well.
We also record what arguments are guaranteed to be non-null as the result
of the conversion. String.valueOf does the null checks for us. The only
corner case to take care of is String.valueOf(Object) returning null itself.
Also, if any conversion happened, then the slot indices in the incoming
arguments are not equal to the final local maps. The only case this may break
is when converting 2-slot long/double argument to 1-slot String. Therefore,
we get away with tracking modified offset, since no conversion can overwrite
the upcoming the argument.
*/
int off = 0;
int modOff = 0;
for (int c = 0; c < arr.length; c++) {
Class> cl = arr[c];
if (cl == String.class) {
if (off != modOff) {
mv.visitIntInsn(getLoadOpcode(cl), off);
mv.visitIntInsn(ASTORE, modOff);
}
} else {
mv.visitIntInsn(getLoadOpcode(cl), off);
mv.visitMethodInsn(
INVOKESTATIC,
"java/lang/String",
"valueOf",
getStringValueOfDesc(cl),
false
);
mv.visitIntInsn(ASTORE, modOff);
arr[c] = String.class;
guaranteedNonNull[c] = cl.isPrimitive();
}
off += getParameterSize(cl);
modOff += getParameterSize(String.class);
}
}
if (mode.isSized()) {
/*
When operating in sized mode (this includes exact mode), it makes sense to make
StringBuilder append chains look familiar to OptimizeStringConcat. For that, we
need to do null-checks early, not make the append chain shape simpler.
*/
int off = 0;
for (RecipeElement el : recipe.getElements()) {
switch (el.getTag()) {
case TAG_CONST:
// Guaranteed non-null, no null check required.
break;
case TAG_ARG:
// Null-checks are needed only for String arguments, and when a previous stage
// did not do implicit null-checks. If a String is null, we eagerly replace it
// with "null" constant. Note, we omit Objects here, because we don't call
// .length() on them down below.
int ac = el.getArgPos();
Class> cl = arr[ac];
if (cl == String.class && !guaranteedNonNull[ac]) {
Label l0 = new Label();
mv.visitIntInsn(ALOAD, off);
mv.visitJumpInsn(IFNONNULL, l0);
mv.visitLdcInsn("null");
mv.visitIntInsn(ASTORE, off);
mv.visitLabel(l0);
}
off += getParameterSize(cl);
break;
default:
throw new StringConcatException("Unhandled tag: " + el.getTag());
}
}
}
// Prepare StringBuilder instance
mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
mv.visitInsn(DUP);
if (mode.isSized()) {
/*
Sized mode requires us to walk through the arguments, and estimate the final length.
In exact mode, this will operate on Strings only. This code would accumulate the
final length on stack.
*/
int len = 0;
int off = 0;
mv.visitInsn(ICONST_0);
for (RecipeElement el : recipe.getElements()) {
switch (el.getTag()) {
case TAG_CONST:
len += el.getValue().length();
break;
case TAG_ARG:
/*
If an argument is String, then we can call .length() on it. Sized/Exact modes have
converted arguments for us. If an argument is primitive, we can provide a guess
for its String representation size.
*/
Class> cl = arr[el.getArgPos()];
if (cl == String.class) {
mv.visitIntInsn(ALOAD, off);
mv.visitMethodInsn(
INVOKEVIRTUAL,
"java/lang/String",
"length",
"()I",
false
);
mv.visitInsn(IADD);
} else if (cl.isPrimitive()) {
len += estimateSize(cl);
}
off += getParameterSize(cl);
break;
default:
throw new StringConcatException("Unhandled tag: " + el.getTag());
}
}
// Constants have non-zero length, mix in
if (len > 0) {
iconst(mv, len);
mv.visitInsn(IADD);
}
mv.visitMethodInsn(
INVOKESPECIAL,
"java/lang/StringBuilder",
" 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 SIMPLE;
} else if (mt.parameterCount() == 1 &&
!mt.parameterType(0).isPrimitive()) {
// One Object argument, one constant
MethodHandle mh = SIMPLE;
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);
}
String className = getClassName(lookup.lookupClass());
MethodType mt = adaptType(concatType);
Recipe rec = new Recipe(recipe, constants);
MethodHandle mh;
if (CACHE_ENABLE) {
Key key = new Key(className, mt, rec);
mh = CACHE.get(key);
if (mh == null) {
mh = generate(lookup, className, mt, rec);
CACHE.put(key, mh);
}
} else {
mh = generate(lookup, className, 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 String getClassName(Class> hostClass) throws StringConcatException {
/*
The generated class is in the same package as the host class as
it's the implementation of the string concatenation for the host class.
When cache is enabled, we want to cache as much as we can.
*/
switch (STRATEGY) {
case BC_SB:
case BC_SB_SIZED:
case BC_SB_SIZED_EXACT: {
if (CACHE_ENABLE) {
String pkgName = hostClass.getPackageName();
return (!pkgName.isEmpty() ? pkgName.replace('.', '/') + "/" : "") + "Stubs$$StringConcat";
} else {
String name = hostClass.isHiddenClass() ? hostClass.getName().replace('/', '_')
: hostClass.getName();
return name.replace('.', '/') + "$$StringConcat";
}
}
case MH_SB_SIZED:
case MH_SB_SIZED_EXACT:
case MH_INLINE_SIZED_EXACT:
// MethodHandle strategies do not need a class name.
return "";
default:
throw new StringConcatException("Concatenation strategy " + STRATEGY + " is not implemented");
}
}
private static MethodHandle generate(Lookup lookup, String className, MethodType mt, Recipe recipe) throws StringConcatException {
try {
switch (STRATEGY) {
case BC_SB:
return BytecodeStringBuilderStrategy.generate(lookup, className, mt, recipe, Mode.DEFAULT);
case BC_SB_SIZED:
return BytecodeStringBuilderStrategy.generate(lookup, className, mt, recipe, Mode.SIZED);
case BC_SB_SIZED_EXACT:
return BytecodeStringBuilderStrategy.generate(lookup, className, 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.
*
*