< prev index next >
src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java
Print this page
rev 54622 : 8222852: Reduce String concat combinator tree shapes by folding constants into prependers
Reviewed-by: shade, plevart
Contributed-by: claes.redestad@oracle.com, peter.levart@gmail.com
*** 29,48 ****
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.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;
import static jdk.internal.org.objectweb.asm.Opcodes.*;
--- 29,45 ----
*** 1530,1559 ****
// 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()) {
return SIMPLE;
! }
! // One element is a constant
! if (mt.parameterCount() == 1 && !mt.parameterType(0).isPrimitive()) {
MethodHandle mh = SIMPLE;
- // Insert constant element
- // First recipe element is a constant
if (recipe.getElements().get(0).getTag() == TAG_CONST &&
! recipe.getElements().get(1).getTag() != TAG_CONST) {
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) {
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.
--- 1527,1562 ----
// 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.
*** 1577,1613 ****
// Drop all remaining parameter types, leave only helper arguments:
MethodHandle mh;
mh = MethodHandles.dropArguments(NEW_STRING, 2, ptypes);
// 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.
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
! );
break;
}
case TAG_ARG: {
! int pos = el.getArgPos();
! MethodHandle prepender = prepender(ptypes[pos]);
! mh = MethodHandles.filterArgumentsWithCombiner(mh, 1, prepender,
1, 0, // indexCoder, storage
2 + pos // selected argument
);
break;
}
default:
throw new StringConcatException("Unhandled tag: " + el.getTag());
}
}
// Fold in byte[] instantiation at argument 0
mh = MethodHandles.foldArgumentsWithCombiner(mh, 0, NEW_ARRAY,
1 // index
);
--- 1580,1657 ----
// Drop all remaining parameter types, leave only helper arguments:
MethodHandle mh;
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: {
! 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: {
!
! 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:
throw new StringConcatException("Unhandled tag: " + el.getTag());
}
}
+ // 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
);
*** 1622,1637 ****
// 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 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);
break;
case TAG_ARG:
int ac = el.getArgPos();
Class<?> argClass = ptypes[ac];
--- 1666,1680 ----
// 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 and after all mixers are combined in is:
// (long, <args>)String = ("indexCoder", <args>)
!
for (RecipeElement el : recipe.getElements()) {
switch (el.getTag()) {
case TAG_CONST:
! // Constants already handled in the code above
break;
case TAG_ARG:
int ac = el.getArgPos();
Class<?> argClass = ptypes[ac];
*** 1659,1687 ****
}
return mh;
}
! private static MethodHandle prepender(Class<?> cl) {
! return PREPENDERS.computeIfAbsent(cl, PREPEND);
}
private static MethodHandle mixer(Class<?> cl) {
return MIXERS.computeIfAbsent(cl, MIX);
}
// This one is deliberately non-lambdified to optimize startup time:
! private static final Function<Class<?>, MethodHandle> PREPEND = new Function<Class<?>, MethodHandle>() {
@Override
public MethodHandle apply(Class<?> c) {
return lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "prepend", long.class, long.class, byte[].class,
! Wrapper.asPrimitiveType(c));
}
};
// This one is deliberately non-lambdified to optimize startup time:
! private static final Function<Class<?>, MethodHandle> MIX = new Function<Class<?>, MethodHandle>() {
@Override
public MethodHandle apply(Class<?> c) {
return lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "mix", long.class, long.class,
Wrapper.asPrimitiveType(c));
}
--- 1702,1732 ----
}
return mh;
}
! 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) {
return MIXERS.computeIfAbsent(cl, MIX);
}
// This one is deliberately non-lambdified to optimize startup time:
! 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,
! 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<>() {
@Override
public MethodHandle apply(Class<?> c) {
return lookupStatic(Lookup.IMPL_LOOKUP, STRING_HELPER, "mix", long.class, long.class,
Wrapper.asPrimitiveType(c));
}
< prev index next >