< 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,20 +29,17 @@
 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.*;

@@ -1530,30 +1527,36 @@
             // 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()) {
+                    !mt.parameterType(1).isPrimitive() &&
+                    recipe.getElements().get(0).getTag() == TAG_ARG &&
+                    recipe.getElements().get(1).getTag() == TAG_ARG) {
+
                     return SIMPLE;
-                }
-                // One element is a constant
-                if (mt.parameterCount() == 1 && !mt.parameterType(0).isPrimitive()) {
+
+                } else if (mt.parameterCount() == 1 &&
+                           !mt.parameterType(0).isPrimitive()) {
+                    // One Object argument, one constant
                     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) {
+                        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
             // 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,37 +1580,78 @@
             // 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: {
-                        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:
                         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,16 +1666,15 @@
             // 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);
+                        // Constants already handled in the code above
                         break;
                     case TAG_ARG:
                         int ac = el.getArgPos();
 
                         Class<?> argClass = ptypes[ac];

@@ -1659,29 +1702,31 @@
             }
 
             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) {
             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>() {
+        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,
                         Wrapper.asPrimitiveType(c));
             }
< prev index next >