< 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 >