8222852: Reduce String concat combinator tree shapes by folding constants into prependers

Co-authored-by: Peter Levart <peter.levart@gmail.com>
Reviewed-by: shade, plevart, forax
This commit is contained in:
Claes Redestad 2019-04-30 12:54:07 +02:00
parent e3aa6f7907
commit e7a6cbbd38
5 changed files with 293 additions and 100 deletions

View file

@ -144,7 +144,7 @@ final class StringConcatHelper {
* @param value boolean value to encode
* @return updated index (coder value retained)
*/
static long prepend(long indexCoder, byte[] buf, boolean value) {
private static long prepend(long indexCoder, byte[] buf, boolean value) {
int index = (int)indexCoder;
if (indexCoder < UTF16) {
if (value) {
@ -178,17 +178,41 @@ final class StringConcatHelper {
}
/**
* Prepends the stringly representation of byte value into buffer,
* 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 byte value to encode
* @param prefix a constant to prepend before value
* @param value boolean value to encode
* @param suffix a constant to prepend after value
* @return updated index (coder value retained)
*/
static long prepend(long indexCoder, byte[] buf, byte value) {
return prepend(indexCoder, buf, (int)value);
static long prepend(long indexCoder, byte[] buf, String prefix, boolean value, String suffix) {
if (suffix != null) indexCoder = prepend(indexCoder, buf, suffix);
indexCoder = prepend(indexCoder, buf, value);
if (prefix != null) indexCoder = prepend(indexCoder, buf, prefix);
return indexCoder;
}
/**
* 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 prefix a constant to prepend before value
* @param value boolean value to encode
* @param suffix a constant to prepend after value
* @return updated index (coder value retained)
*/
static long prepend(long indexCoder, byte[] buf, String prefix, byte value, String suffix) {
if (suffix != null) indexCoder = prepend(indexCoder, buf, suffix);
indexCoder = prepend(indexCoder, buf, (int)value);
if (prefix != null) indexCoder = prepend(indexCoder, buf, prefix);
return indexCoder;
}
/**
@ -201,7 +225,7 @@ final class StringConcatHelper {
* @param value char value to encode
* @return updated index (coder value retained)
*/
static long prepend(long indexCoder, byte[] buf, char value) {
private static long prepend(long indexCoder, byte[] buf, char value) {
if (indexCoder < UTF16) {
buf[(int)(--indexCoder)] = (byte) (value & 0xFF);
} else {
@ -211,17 +235,41 @@ final class StringConcatHelper {
}
/**
* Prepends the stringly representation of short value into buffer,
* 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 short value to encode
* @param prefix a constant to prepend before value
* @param value boolean value to encode
* @param suffix a constant to prepend after value
* @return updated index (coder value retained)
*/
static long prepend(long indexCoder, byte[] buf, short value) {
return prepend(indexCoder, buf, (int)value);
static long prepend(long indexCoder, byte[] buf, String prefix, char value, String suffix) {
if (suffix != null) indexCoder = prepend(indexCoder, buf, suffix);
indexCoder = prepend(indexCoder, buf, value);
if (prefix != null) indexCoder = prepend(indexCoder, buf, prefix);
return indexCoder;
}
/**
* 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 prefix a constant to prepend before value
* @param value boolean value to encode
* @param suffix a constant to prepend after value
* @return updated index (coder value retained)
*/
static long prepend(long indexCoder, byte[] buf, String prefix, short value, String suffix) {
if (suffix != null) indexCoder = prepend(indexCoder, buf, suffix);
indexCoder = prepend(indexCoder, buf, (int)value);
if (prefix != null) indexCoder = prepend(indexCoder, buf, prefix);
return indexCoder;
}
/**
@ -234,7 +282,7 @@ final class StringConcatHelper {
* @param value integer value to encode
* @return updated index (coder value retained)
*/
static long prepend(long indexCoder, byte[] buf, int value) {
private static long prepend(long indexCoder, byte[] buf, int value) {
if (indexCoder < UTF16) {
return Integer.getChars(value, (int)indexCoder, buf);
} else {
@ -242,6 +290,25 @@ final class StringConcatHelper {
}
}
/**
* 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 prefix a constant to prepend before value
* @param value boolean value to encode
* @param suffix a constant to prepend after value
* @return updated index (coder value retained)
*/
static long prepend(long indexCoder, byte[] buf, String prefix, int value, String suffix) {
if (suffix != null) indexCoder = prepend(indexCoder, buf, suffix);
indexCoder = prepend(indexCoder, buf, value);
if (prefix != null) indexCoder = prepend(indexCoder, buf, prefix);
return indexCoder;
}
/**
* Prepends the stringly representation of long value into buffer,
* given the coder and final index. Index is measured in chars, not in bytes!
@ -252,7 +319,7 @@ final class StringConcatHelper {
* @param value long value to encode
* @return updated index (coder value retained)
*/
static long prepend(long indexCoder, byte[] buf, long value) {
private static long prepend(long indexCoder, byte[] buf, long value) {
if (indexCoder < UTF16) {
return Long.getChars(value, (int)indexCoder, buf);
} else {
@ -260,6 +327,25 @@ final class StringConcatHelper {
}
}
/**
* 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 prefix a constant to prepend before value
* @param value boolean value to encode
* @param suffix a constant to prepend after value
* @return updated index (coder value retained)
*/
static long prepend(long indexCoder, byte[] buf, String prefix, long value, String suffix) {
if (suffix != null) indexCoder = prepend(indexCoder, buf, suffix);
indexCoder = prepend(indexCoder, buf, value);
if (prefix != null) indexCoder = prepend(indexCoder, buf, prefix);
return indexCoder;
}
/**
* Prepends the stringly representation of String value into buffer,
* given the coder and final index. Index is measured in chars, not in bytes!
@ -270,7 +356,7 @@ final class StringConcatHelper {
* @param value String value to encode
* @return updated index (coder value retained)
*/
static long prepend(long indexCoder, byte[] buf, String value) {
private static long prepend(long indexCoder, byte[] buf, String value) {
indexCoder -= value.length();
if (indexCoder < UTF16) {
value.getBytes(buf, (int)indexCoder, String.LATIN1);
@ -280,6 +366,25 @@ final class StringConcatHelper {
return indexCoder;
}
/**
* 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 prefix a constant to prepend before value
* @param value boolean value to encode
* @param suffix a constant to prepend after value
* @return updated index (coder value retained)
*/
static long prepend(long indexCoder, byte[] buf, String prefix, String value, String suffix) {
if (suffix != null) indexCoder = prepend(indexCoder, buf, suffix);
indexCoder = prepend(indexCoder, buf, value);
if (prefix != null) indexCoder = prepend(indexCoder, buf, prefix);
return indexCoder;
}
/**
* Instantiates the String with given buffer and coder
* @param buf buffer to use

View file

@ -31,16 +31,13 @@ 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.ForceInline;
import sun.invoke.util.Wrapper;
import sun.security.action.GetPropertyAction;
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.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
@ -1531,27 +1528,33 @@ public final class StringConcatFactory {
if (recipe.getElements().size() == 2) {
// Two object arguments
if (mt.parameterCount() == 2 &&
!mt.parameterType(0).isPrimitive() &&
!mt.parameterType(1).isPrimitive()) {
return SIMPLE;
}
// One element is a constant
if (mt.parameterCount() == 1 && !mt.parameterType(0).isPrimitive()) {
MethodHandle mh = SIMPLE;
// Insert constant element
!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;
// First recipe element is a constant
if (recipe.getElements().get(0).getTag() == TAG_CONST &&
recipe.getElements().get(1).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_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
}
// else... fall-through to slow-path
}
// Create filters and obtain filtered parameter types. Filters would be used in the beginning
@ -1579,26 +1582,49 @@ public final class StringConcatFactory {
mh = MethodHandles.dropArguments(NEW_STRING, 2, ptypes);
long initialLengthCoder = INITIAL_CODER;
// 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.
// We need one prepender per argument, but also need to fold in constants. We do so by greedily
// create 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.
String prefixConstant = null, suffixConstant = null;
int pos = -1;
for (RecipeElement el : recipe.getElements()) {
// Do the prepend, and put "new" index at index 1
switch (el.getTag()) {
case TAG_CONST: {
MethodHandle prepender = MethodHandles.insertArguments(prepender(String.class), 2, el.getValue());
mh = MethodHandles.filterArgumentsWithCombiner(mh, 1, prepender,
1, 0 // indexCoder, storage
);
String constantValue = el.getValue();
// Eagerly update the initialLengthCoder value
initialLengthCoder = (long)mixer(String.class).invoke(initialLengthCoder, constantValue);
if (pos < 0) {
// Collecting into prefixConstant
prefixConstant = prefixConstant == null ? constantValue : prefixConstant + constantValue;
} else {
// Collecting into suffixConstant
suffixConstant = suffixConstant == null ? constantValue : suffixConstant + constantValue;
}
break;
}
case TAG_ARG: {
int pos = el.getArgPos();
MethodHandle prepender = prepender(ptypes[pos]);
mh = MethodHandles.filterArgumentsWithCombiner(mh, 1, prepender,
if (pos >= 0) {
// Flush the previous non-constant arg with any prefix/suffix constant
mh = MethodHandles.filterArgumentsWithCombiner(
mh, 1,
prepender(prefixConstant, ptypes[pos], suffixConstant),
1, 0, // indexCoder, storage
2 + pos // selected argument
);
);
prefixConstant = suffixConstant = null;
}
// Mark the pos of next non-constant arg
pos = el.getArgPos();
break;
}
default:
@ -1606,6 +1632,24 @@ public final class StringConcatFactory {
}
}
// Insert any trailing args, constants
if (pos >= 0) {
mh = MethodHandles.filterArgumentsWithCombiner(
mh, 1,
prepender(prefixConstant, ptypes[pos], suffixConstant),
1, 0, // indexCoder, storage
2 + pos // selected argument
);
} else if (prefixConstant != null) {
assert (suffixConstant == null);
// Sole prefixConstant can only happen if there were no non-constant arguments
mh = MethodHandles.filterArgumentsWithCombiner(
mh, 1,
MethodHandles.insertArguments(prepender(null, String.class, null), 2, prefixConstant),
1, 0 // indexCoder, storage
);
}
// Fold in byte[] instantiation at argument 0
mh = MethodHandles.foldArgumentsWithCombiner(mh, 0, NEW_ARRAY,
1 // index
@ -1624,12 +1668,11 @@ public final class StringConcatFactory {
//
// The method handle shape before and after all mixers are combined in is:
// (long, <args>)String = ("indexCoder", <args>)
long initialLengthCoder = INITIAL_CODER;
for (RecipeElement el : recipe.getElements()) {
switch (el.getTag()) {
case TAG_CONST:
String constant = el.getValue();
initialLengthCoder = (long)mixer(String.class).invoke(initialLengthCoder, constant);
// Constants already handled in the code above
break;
case TAG_ARG:
int ac = el.getArgPos();
@ -1661,8 +1704,10 @@ public final class StringConcatFactory {
return mh;
}
private static MethodHandle prepender(Class<?> cl) {
return PREPENDERS.computeIfAbsent(cl, PREPEND);
private static MethodHandle prepender(String prefix, Class<?> cl, String suffix) {
return MethodHandles.insertArguments(
MethodHandles.insertArguments(
PREPENDERS.computeIfAbsent(cl, PREPEND),2, prefix), 3, suffix);
}
private static MethodHandle mixer(Class<?> cl) {
@ -1670,16 +1715,16 @@ public final class StringConcatFactory {
}
// This one is deliberately non-lambdified to optimize startup time:
private static final Function<Class<?>, MethodHandle> PREPEND = new Function<Class<?>, MethodHandle>() {
private static final Function<Class<?>, MethodHandle> PREPEND = new Function<>() {
@Override
public MethodHandle apply(Class<?> c) {
return lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "prepend", long.class, long.class, byte[].class,
Wrapper.asPrimitiveType(c));
String.class, Wrapper.asPrimitiveType(c), String.class);
}
};
// This one is deliberately non-lambdified to optimize startup time:
private static final Function<Class<?>, MethodHandle> MIX = new Function<Class<?>, MethodHandle>() {
private static final Function<Class<?>, MethodHandle> MIX = new Function<>() {
@Override
public MethodHandle apply(Class<?> c) {
return lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "mix", long.class, long.class,