mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 12:09:14 +00:00
8368024: Remove StringConcatFactory#generateMHInlineCopy
Reviewed-by: redestad
This commit is contained in:
parent
29908148f8
commit
e122f4dd0d
@ -3710,7 +3710,7 @@ public final class String
|
||||
if (len < 0L || (len <<= coder) != (int) len) {
|
||||
throw new OutOfMemoryError("Requested string length exceeds VM limit");
|
||||
}
|
||||
byte[] value = StringConcatHelper.newArray(len);
|
||||
byte[] value = StringConcatHelper.newArray((int) len);
|
||||
|
||||
int off = 0;
|
||||
prefix.getBytes(value, off, coder); off += prefix.length();
|
||||
|
||||
@ -141,262 +141,6 @@ final class StringConcatHelper {
|
||||
// no instantiation
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the coder for the character.
|
||||
* @param value character
|
||||
* @return coder
|
||||
*/
|
||||
static long coder(char value) {
|
||||
return StringLatin1.canEncode(value) ? LATIN1 : UTF16;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for overflow, throw exception on overflow.
|
||||
*
|
||||
* @param lengthCoder String length with coder packed into higher bits
|
||||
* the upper word.
|
||||
* @return the given parameter value, if valid
|
||||
*/
|
||||
private static long checkOverflow(long lengthCoder) {
|
||||
if ((int)lengthCoder >= 0) {
|
||||
return lengthCoder;
|
||||
}
|
||||
throw new OutOfMemoryError("Overflow: String length out of range");
|
||||
}
|
||||
|
||||
/**
|
||||
* Mix value length and coder into current length and coder.
|
||||
* @param lengthCoder String length with coder packed into higher bits
|
||||
* the upper word.
|
||||
* @param value value to mix in
|
||||
* @return new length and coder
|
||||
*/
|
||||
static long mix(long lengthCoder, boolean value) {
|
||||
return checkOverflow(lengthCoder + (value ? 4 : 5));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mix value length and coder into current length and coder.
|
||||
* @param lengthCoder String length with coder packed into higher bits
|
||||
* the upper word.
|
||||
* @param value value to mix in
|
||||
* @return new length and coder
|
||||
*/
|
||||
static long mix(long lengthCoder, char value) {
|
||||
return checkOverflow(lengthCoder + 1) | coder(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mix value length and coder into current length and coder.
|
||||
* @param lengthCoder String length with coder packed into higher bits
|
||||
* the upper word.
|
||||
* @param value value to mix in
|
||||
* @return new length and coder
|
||||
*/
|
||||
static long mix(long lengthCoder, int value) {
|
||||
return checkOverflow(lengthCoder + DecimalDigits.stringSize(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mix value length and coder into current length and coder.
|
||||
* @param lengthCoder String length with coder packed into higher bits
|
||||
* the upper word.
|
||||
* @param value value to mix in
|
||||
* @return new length and coder
|
||||
*/
|
||||
static long mix(long lengthCoder, long value) {
|
||||
return checkOverflow(lengthCoder + DecimalDigits.stringSize(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mix value length and coder into current length and coder.
|
||||
* @param lengthCoder String length with coder packed into higher bits
|
||||
* the upper word.
|
||||
* @param value value to mix in
|
||||
* @return new length and coder
|
||||
*/
|
||||
static long mix(long lengthCoder, String value) {
|
||||
lengthCoder += value.length();
|
||||
if (!value.isLatin1()) {
|
||||
lengthCoder |= UTF16;
|
||||
}
|
||||
return checkOverflow(lengthCoder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepends constant and the stringly representation of value into buffer,
|
||||
* given the coder and final index. Index is measured in chars, not in bytes!
|
||||
*
|
||||
* @param indexCoder final char index in the buffer, along with coder packed
|
||||
* into higher bits.
|
||||
* @param buf buffer to append to
|
||||
* @param value boolean value to encode
|
||||
* @param prefix a constant to prepend before value
|
||||
* @return updated index (coder value retained)
|
||||
*/
|
||||
static long prepend(long indexCoder, byte[] buf, boolean value, String prefix) {
|
||||
int index = (int)indexCoder;
|
||||
if (indexCoder < UTF16) {
|
||||
if (value) {
|
||||
index -= 4;
|
||||
buf[index] = 't';
|
||||
buf[index + 1] = 'r';
|
||||
buf[index + 2] = 'u';
|
||||
buf[index + 3] = 'e';
|
||||
} else {
|
||||
index -= 5;
|
||||
buf[index] = 'f';
|
||||
buf[index + 1] = 'a';
|
||||
buf[index + 2] = 'l';
|
||||
buf[index + 3] = 's';
|
||||
buf[index + 4] = 'e';
|
||||
}
|
||||
index -= prefix.length();
|
||||
prefix.getBytes(buf, index, String.LATIN1);
|
||||
return index;
|
||||
} else {
|
||||
if (value) {
|
||||
index -= 4;
|
||||
StringUTF16.putChar(buf, index, 't');
|
||||
StringUTF16.putChar(buf, index + 1, 'r');
|
||||
StringUTF16.putChar(buf, index + 2, 'u');
|
||||
StringUTF16.putChar(buf, index + 3, 'e');
|
||||
} else {
|
||||
index -= 5;
|
||||
StringUTF16.putChar(buf, index, 'f');
|
||||
StringUTF16.putChar(buf, index + 1, 'a');
|
||||
StringUTF16.putChar(buf, index + 2, 'l');
|
||||
StringUTF16.putChar(buf, index + 3, 's');
|
||||
StringUTF16.putChar(buf, index + 4, 'e');
|
||||
}
|
||||
index -= prefix.length();
|
||||
prefix.getBytes(buf, index, String.UTF16);
|
||||
return index | UTF16;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepends constant and the stringly representation of value into buffer,
|
||||
* given the coder and final index. Index is measured in chars, not in bytes!
|
||||
*
|
||||
* @param indexCoder final char index in the buffer, along with coder packed
|
||||
* into higher bits.
|
||||
* @param buf buffer to append to
|
||||
* @param value char value to encode
|
||||
* @param prefix a constant to prepend before value
|
||||
* @return updated index (coder value retained)
|
||||
*/
|
||||
static long prepend(long indexCoder, byte[] buf, char value, String prefix) {
|
||||
int index = (int)indexCoder;
|
||||
if (indexCoder < UTF16) {
|
||||
buf[--index] = (byte) (value & 0xFF);
|
||||
index -= prefix.length();
|
||||
prefix.getBytes(buf, index, String.LATIN1);
|
||||
return index;
|
||||
} else {
|
||||
StringUTF16.putChar(buf, --index, value);
|
||||
index -= prefix.length();
|
||||
prefix.getBytes(buf, index, String.UTF16);
|
||||
return index | UTF16;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepends constant and the stringly representation of value into buffer,
|
||||
* given the coder and final index. Index is measured in chars, not in bytes!
|
||||
*
|
||||
* @param indexCoder final char index in the buffer, along with coder packed
|
||||
* into higher bits.
|
||||
* @param buf buffer to append to
|
||||
* @param value int value to encode
|
||||
* @param prefix a constant to prepend before value
|
||||
* @return updated index (coder value retained)
|
||||
*/
|
||||
static long prepend(long indexCoder, byte[] buf, int value, String prefix) {
|
||||
int index = (int)indexCoder;
|
||||
if (indexCoder < UTF16) {
|
||||
index = DecimalDigits.uncheckedGetCharsLatin1(value, index, buf);
|
||||
index -= prefix.length();
|
||||
prefix.getBytes(buf, index, String.LATIN1);
|
||||
return index;
|
||||
} else {
|
||||
index = DecimalDigits.uncheckedGetCharsUTF16(value, index, buf);
|
||||
index -= prefix.length();
|
||||
prefix.getBytes(buf, index, String.UTF16);
|
||||
return index | UTF16;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepends constant and the stringly representation of value into buffer,
|
||||
* given the coder and final index. Index is measured in chars, not in bytes!
|
||||
*
|
||||
* @param indexCoder final char index in the buffer, along with coder packed
|
||||
* into higher bits.
|
||||
* @param buf buffer to append to
|
||||
* @param value long value to encode
|
||||
* @param prefix a constant to prepend before value
|
||||
* @return updated index (coder value retained)
|
||||
*/
|
||||
static long prepend(long indexCoder, byte[] buf, long value, String prefix) {
|
||||
int index = (int)indexCoder;
|
||||
if (indexCoder < UTF16) {
|
||||
index = DecimalDigits.uncheckedGetCharsLatin1(value, index, buf);
|
||||
index -= prefix.length();
|
||||
prefix.getBytes(buf, index, String.LATIN1);
|
||||
return index;
|
||||
} else {
|
||||
index = DecimalDigits.uncheckedGetCharsUTF16(value, index, buf);
|
||||
index -= prefix.length();
|
||||
prefix.getBytes(buf, index, String.UTF16);
|
||||
return index | UTF16;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepends constant and the stringly representation of value into buffer,
|
||||
* given the coder and final index. Index is measured in chars, not in bytes!
|
||||
*
|
||||
* @param indexCoder final char index in the buffer, along with coder packed
|
||||
* into higher bits.
|
||||
* @param buf buffer to append to
|
||||
* @param value boolean value to encode
|
||||
* @param prefix a constant to prepend before value
|
||||
* @return updated index (coder value retained)
|
||||
*/
|
||||
static long prepend(long indexCoder, byte[] buf, String value, String prefix) {
|
||||
int index = ((int)indexCoder) - value.length();
|
||||
if (indexCoder < UTF16) {
|
||||
value.getBytes(buf, index, String.LATIN1);
|
||||
index -= prefix.length();
|
||||
prefix.getBytes(buf, index, String.LATIN1);
|
||||
return index;
|
||||
} else {
|
||||
value.getBytes(buf, index, String.UTF16);
|
||||
index -= prefix.length();
|
||||
prefix.getBytes(buf, index, String.UTF16);
|
||||
return index | UTF16;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates the String with given buffer and coder
|
||||
* @param buf buffer to use
|
||||
* @param indexCoder remaining index (should be zero) and coder
|
||||
* @return String resulting string
|
||||
*/
|
||||
static String newString(byte[] buf, long indexCoder) {
|
||||
// Use the private, non-copying constructor (unsafe!)
|
||||
if (indexCoder == LATIN1) {
|
||||
return new String(buf, String.LATIN1);
|
||||
} else if (indexCoder == UTF16) {
|
||||
return new String(buf, String.UTF16);
|
||||
} else {
|
||||
throw new InternalError("Storage is not completely initialized, " +
|
||||
(int)indexCoder + " bytes left");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a simple concatenation between two objects. Added for startup
|
||||
* performance, but also demonstrates the code that would be emitted by
|
||||
@ -466,10 +210,6 @@ final class StringConcatHelper {
|
||||
return (value == null || (s = value.toString()) == null) ? "null" : s;
|
||||
}
|
||||
|
||||
private static final long LATIN1 = (long)String.LATIN1 << 32;
|
||||
|
||||
private static final long UTF16 = (long)String.UTF16 << 32;
|
||||
|
||||
private static final Unsafe UNSAFE = Unsafe.getUnsafe();
|
||||
|
||||
static String stringOf(float value) {
|
||||
@ -530,41 +270,6 @@ final class StringConcatHelper {
|
||||
return checkOverflow(length + value.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates an uninitialized byte array based on the length and coder
|
||||
* information, then prepends the given suffix string at the end of the
|
||||
* byte array before returning it. The calling code must adjust the
|
||||
* indexCoder so that it's taken the coder of the suffix into account, but
|
||||
* subtracted the length of the suffix.
|
||||
*
|
||||
* @param suffix
|
||||
* @param indexCoder
|
||||
* @return the newly allocated byte array
|
||||
*/
|
||||
@ForceInline
|
||||
static byte[] newArrayWithSuffix(String suffix, long indexCoder) {
|
||||
byte[] buf = newArray(indexCoder + suffix.length());
|
||||
if (indexCoder < UTF16) {
|
||||
suffix.getBytes(buf, (int)indexCoder, String.LATIN1);
|
||||
} else {
|
||||
suffix.getBytes(buf, (int)indexCoder, String.UTF16);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates an uninitialized byte array based on the length and coder information
|
||||
* in indexCoder
|
||||
* @param indexCoder
|
||||
* @return the newly allocated byte array
|
||||
*/
|
||||
@ForceInline
|
||||
static byte[] newArray(long indexCoder) {
|
||||
byte coder = (byte)(indexCoder >> 32);
|
||||
int index = ((int)indexCoder) << coder;
|
||||
return newArray(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates an uninitialized byte array based on the length
|
||||
* @param length
|
||||
@ -578,14 +283,6 @@ final class StringConcatHelper {
|
||||
return (byte[]) UNSAFE.allocateUninitializedArray(byte.class, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the initial coder for the String.
|
||||
* @return initial coder, adjusted into the upper half
|
||||
*/
|
||||
static long initialCoder() {
|
||||
return String.COMPACT_STRINGS ? LATIN1 : UTF16;
|
||||
}
|
||||
|
||||
static MethodHandle lookupStatic(String name, MethodType methodType) {
|
||||
try {
|
||||
return MethodHandles.lookup()
|
||||
@ -603,7 +300,8 @@ final class StringConcatHelper {
|
||||
* subtracted the length of the suffix.
|
||||
*
|
||||
* @param suffix
|
||||
* @param indexCoder
|
||||
* @param index final char index in the buffer
|
||||
* @param coder coder of the buffer
|
||||
* @return the newly allocated byte array
|
||||
*/
|
||||
@ForceInline
|
||||
|
||||
@ -2185,18 +2185,6 @@ public final class System {
|
||||
return StringConcatHelper.lookupStatic(name, methodType);
|
||||
}
|
||||
|
||||
public long stringConcatInitialCoder() {
|
||||
return StringConcatHelper.initialCoder();
|
||||
}
|
||||
|
||||
public long stringConcatMix(long lengthCoder, String constant) {
|
||||
return StringConcatHelper.mix(lengthCoder, constant);
|
||||
}
|
||||
|
||||
public long stringConcatMix(long lengthCoder, char value) {
|
||||
return StringConcatHelper.mix(lengthCoder, value);
|
||||
}
|
||||
|
||||
public Object uncheckedStringConcat1(String[] constants) {
|
||||
return new StringConcatHelper.Concat1(constants);
|
||||
}
|
||||
|
||||
@ -37,7 +37,6 @@ import jdk.internal.util.ReferenceKey;
|
||||
import jdk.internal.util.ReferencedKeyMap;
|
||||
import jdk.internal.vm.annotation.AOTSafeClassInitializer;
|
||||
import jdk.internal.vm.annotation.Stable;
|
||||
import sun.invoke.util.Wrapper;
|
||||
|
||||
import java.lang.classfile.Annotation;
|
||||
import java.lang.classfile.ClassBuilder;
|
||||
@ -119,14 +118,10 @@ import static java.lang.invoke.MethodType.methodType;
|
||||
*/
|
||||
@AOTSafeClassInitializer
|
||||
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;
|
||||
|
||||
@ -391,9 +386,6 @@ public final class StringConcatFactory {
|
||||
|
||||
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);
|
||||
@ -518,385 +510,6 @@ public final class StringConcatFactory {
|
||||
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;
|
||||
@ -908,42 +521,11 @@ public final class StringConcatFactory {
|
||||
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;
|
||||
@ -1027,38 +609,6 @@ public final class StringConcatFactory {
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
@ -452,21 +452,6 @@ public interface JavaLangAccess {
|
||||
*/
|
||||
MethodHandle stringConcatHelper(String name, MethodType methodType);
|
||||
|
||||
/**
|
||||
* Get the string concat initial coder
|
||||
*/
|
||||
long stringConcatInitialCoder();
|
||||
|
||||
/**
|
||||
* Update lengthCoder for constant
|
||||
*/
|
||||
long stringConcatMix(long lengthCoder, String constant);
|
||||
|
||||
/**
|
||||
* Mix value length and coder into current length and coder.
|
||||
*/
|
||||
long stringConcatMix(long lengthCoder, char value);
|
||||
|
||||
/**
|
||||
* Creates helper for string concatenation.
|
||||
* <p>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user