mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 06:45:07 +02:00
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:
parent
e3aa6f7907
commit
e7a6cbbd38
5 changed files with 293 additions and 100 deletions
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue