changeset 59826:3be168f5cf1b

8247681: Improve bootstrapping of unary concatenations Reviewed-by: jlaskey, psandoz
author redestad
date Wed, 17 Jun 2020 19:36:26 +0200
parents 6629aa057963
children 2a342cba9cc8
files src/java.base/share/classes/java/lang/StringConcatHelper.java src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java test/jdk/java/lang/String/concat/ImplicitStringConcatShapes-head.template test/jdk/java/lang/String/concat/ImplicitStringConcatShapes.java test/jdk/java/lang/String/concat/StringConcatFactoryInvariants.java test/micro/org/openjdk/bench/java/lang/StringConcat.java
diffstat 6 files changed, 149 insertions(+), 19 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.base/share/classes/java/lang/StringConcatHelper.java	Wed Jun 17 09:24:28 2020 -0700
+++ b/src/java.base/share/classes/java/lang/StringConcatHelper.java	Wed Jun 17 19:36:26 2020 +0200
@@ -427,6 +427,22 @@
     }
 
     /**
+     * Produce a String from a concatenation of single argument, which we
+     * end up using for trivial concatenations like {@code "" + arg}.
+     *
+     * This will always create a new Object to comply with JLS 15.18.1:
+     * "The String object is newly created unless the expression is a
+     * compile-time constant expression".
+     *
+     * @param arg           the only argument
+     * @return String       resulting string
+     */
+    @ForceInline
+    static String newStringOf(Object arg) {
+        return new String(stringOf(arg));
+    }
+
+    /**
      * We need some additional conversion for Objects in general, because
      * {@code String.valueOf(Object)} may return null. String conversion rules
      * in Java state we need to produce "null" String in this case, so we
--- a/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java	Wed Jun 17 09:24:28 2020 -0700
+++ b/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java	Wed Jun 17 19:36:26 2020 +0200
@@ -447,9 +447,18 @@
      */
     private static MethodHandle generateMHInlineCopy(MethodType mt, List<String> elements) {
 
-        // Fast-path two-argument Object + Object concatenations
+        // Fast-path unary concatenations
+        if (elements.size() == 1) {
+            String s0 = elements.get(0);
+            if (s0 == null) {
+                return unaryConcat(mt.parameterType(0));
+            } else {
+                return MethodHandles.insertArguments(unaryConcat(Object.class), 0, s0);
+            }
+        }
+        // Fast-path binary concatenations
         if (elements.size() == 2) {
-            // Two object arguments
+            // Two arguments
             String s0 = elements.get(0);
             String s1 = elements.get(1);
 
@@ -459,20 +468,22 @@
                     s0 == null &&
                     s1 == null) {
                 return simpleConcat();
-            } else if (mt.parameterCount() == 1 &&
-                    !mt.parameterType(0).isPrimitive()) {
-
-                // One Object argument, one constant
-                MethodHandle mh = simpleConcat();
-
-                if (s0 != null && s1 == null) {
-                    // First recipe element is a constant
-                    return MethodHandles.insertArguments(mh, 0, s0);
-
-                } else if (s1 != null && s0 == null) {
-                    // Second recipe element is a constant
-                    return MethodHandles.insertArguments(mh, 1, s1);
-
+            } else if (mt.parameterCount() == 1) {
+                // One argument, one constant
+                String constant;
+                int constIdx;
+                if (s1 == null) {
+                    constant = s0;
+                    constIdx = 0;
+                } else {
+                    constant = s1;
+                    constIdx = 1;
+                }
+                if (constant.isEmpty()) {
+                    return unaryConcat(mt.parameterType(0));
+                } else if (!mt.parameterType(0).isPrimitive()) {
+                    // Non-primitive argument
+                    return MethodHandles.insertArguments(simpleConcat(), constIdx, constant);
                 }
             }
             // else... fall-through to slow-path
@@ -732,6 +743,76 @@
         return mh;
     }
 
+    private @Stable static MethodHandle INT_STRINGIFIER;
+    private static MethodHandle intStringifier() {
+        MethodHandle mh = INT_STRINGIFIER;
+        if (mh == null) {
+            INT_STRINGIFIER = mh =
+                    lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, int.class);
+        }
+        return mh;
+    }
+
+    private @Stable static MethodHandle LONG_STRINGIFIER;
+    private static MethodHandle longStringifier() {
+        MethodHandle mh = LONG_STRINGIFIER;
+        if (mh == null) {
+            LONG_STRINGIFIER = mh =
+                    lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, long.class);
+        }
+        return mh;
+    }
+
+    private @Stable static MethodHandle CHAR_STRINGIFIER;
+    private static MethodHandle charStringifier() {
+        MethodHandle mh = CHAR_STRINGIFIER;
+        if (mh == null) {
+            CHAR_STRINGIFIER = mh =
+                    lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, char.class);
+        }
+        return mh;
+    }
+
+    private @Stable static MethodHandle BOOLEAN_STRINGIFIER;
+    private static MethodHandle booleanStringifier() {
+        MethodHandle mh = BOOLEAN_STRINGIFIER;
+        if (mh == null) {
+            BOOLEAN_STRINGIFIER = mh =
+                    lookupStatic(MethodHandles.publicLookup(), String.class, "valueOf", String.class, boolean.class);
+        }
+        return mh;
+    }
+
+    private @Stable static MethodHandle NEW_STRINGIFIER;
+    private static MethodHandle newStringifier() {
+        MethodHandle mh = NEW_STRINGIFIER;
+        if (mh == null) {
+            NEW_STRINGIFIER = mh = JLA.stringConcatHelper("newStringOf",
+                    methodType(String.class, Object.class));
+        }
+        return mh;
+    }
+
+    private static MethodHandle unaryConcat(Class<?> cl) {
+        if (!cl.isPrimitive()) {
+            return newStringifier();
+        } else if (cl == int.class || cl == short.class || cl == byte.class) {
+            return intStringifier();
+        } else if (cl == long.class) {
+            return longStringifier();
+        } else if (cl == char.class) {
+            return charStringifier();
+        } else if (cl == boolean.class) {
+            return booleanStringifier();
+        } else if (cl == float.class) {
+            return floatStringifier();
+        } else if (cl == double.class) {
+            return doubleStringifier();
+        } else {
+            throw new InternalError("Unhandled type for unary concatenation: " + cl);
+        }
+    }
+
     private static final ConcurrentMap<Class<?>, MethodHandle> PREPENDERS;
     private static final ConcurrentMap<Class<?>, MethodHandle> NULL_PREPENDERS;
     private static final ConcurrentMap<Class<?>, MethodHandle> MIXERS;
--- a/test/jdk/java/lang/String/concat/ImplicitStringConcatShapes-head.template	Wed Jun 17 09:24:28 2020 -0700
+++ b/test/jdk/java/lang/String/concat/ImplicitStringConcatShapes-head.template	Wed Jun 17 19:36:26 2020 +0200
@@ -24,7 +24,7 @@
 /**
  * @test
  * @summary Test implicit String concatenations, multiple shapes.
- * @bug 8148483 8245959
+ * @bug 8148483 8245959 8247681
  *
  * @compile ImplicitStringConcatShapes.java
  * @run main/othervm -Xverify:all ImplicitStringConcatShapes
--- a/test/jdk/java/lang/String/concat/ImplicitStringConcatShapes.java	Wed Jun 17 09:24:28 2020 -0700
+++ b/test/jdk/java/lang/String/concat/ImplicitStringConcatShapes.java	Wed Jun 17 19:36:26 2020 +0200
@@ -24,7 +24,7 @@
 /**
  * @test
  * @summary Test implicit String concatenations, multiple shapes.
- * @bug 8148483 8245959
+ * @bug 8148483 8245959 8247681
  *
  * @compile ImplicitStringConcatShapes.java
  * @run main/othervm -Xverify:all ImplicitStringConcatShapes
--- a/test/jdk/java/lang/String/concat/StringConcatFactoryInvariants.java	Wed Jun 17 09:24:28 2020 -0700
+++ b/test/jdk/java/lang/String/concat/StringConcatFactoryInvariants.java	Wed Jun 17 19:36:26 2020 +0200
@@ -28,7 +28,7 @@
 /**
  * @test
  * @summary Test input invariants for StringConcatFactory
- * @bug 8246152
+ * @bug 8246152 8247681
  *
  * @compile StringConcatFactoryInvariants.java
  *
@@ -46,6 +46,7 @@
         MethodType mt = MethodType.methodType(String.class, String.class, int.class);
         String recipe = "" + TAG_ARG + TAG_ARG + TAG_CONST;
         Object[][] constants = new Object[][] {
+                new String[] { "" },
                 new String[] { "bar" },
                 new Integer[] { 1 },
                 new Short[] { 2 },
@@ -60,6 +61,7 @@
         // The string representation that should end up if the corresponding
         // Object[] in constants is used as an argument to makeConcatWithConstants
         String[] constantString = new String[] {
+                "",
                 "bar",
                 "1",
                 "2",
@@ -116,6 +118,21 @@
             }
         }
 
+        // Check unary expressions with pre- and postfix constants
+        {
+            String constArgRecipe = "" + TAG_CONST + TAG_ARG;
+            String argConstRecipe = "" + TAG_ARG + TAG_CONST;
+            MethodType unaryMt = MethodType.methodType(String.class, String.class);
+
+            for (int i = 0; i < constants.length; i++) {
+                CallSite prefixCS = StringConcatFactory.makeConcatWithConstants(lookup, methodName, unaryMt, constArgRecipe, constants[i]);
+                test(constantString[i].concat("foo"), (String) prefixCS.getTarget().invokeExact("foo"));
+
+                CallSite postfixCS = StringConcatFactory.makeConcatWithConstants(lookup, methodName, unaryMt, argConstRecipe, constants[i]);
+                test("foo".concat(constantString[i]), (String) postfixCS.getTarget().invokeExact("foo"));
+            }
+        }
+
         // Simple factory, check for nulls:
         failNPE("Lookup is null",
                 () -> StringConcatFactory.makeConcat(null, methodName, mt));
--- a/test/micro/org/openjdk/bench/java/lang/StringConcat.java	Wed Jun 17 09:24:28 2020 -0700
+++ b/test/micro/org/openjdk/bench/java/lang/StringConcat.java	Wed Jun 17 19:36:26 2020 +0200
@@ -24,11 +24,14 @@
 
 import org.openjdk.jmh.annotations.Benchmark;
 import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Measurement;
 import org.openjdk.jmh.annotations.Mode;
 import org.openjdk.jmh.annotations.OutputTimeUnit;
 import org.openjdk.jmh.annotations.Param;
 import org.openjdk.jmh.annotations.Scope;
 import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
 
 import java.util.concurrent.TimeUnit;
 
@@ -38,6 +41,9 @@
 @BenchmarkMode(Mode.AverageTime)
 @OutputTimeUnit(TimeUnit.NANOSECONDS)
 @State(Scope.Thread)
+@Warmup(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
+@Measurement(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
+@Fork(3)
 public class StringConcat {
 
     @Param("4711")
@@ -74,6 +80,16 @@
     }
 
     @Benchmark
+    public String concatEmptyConstInt() {
+        return "" + intValue;
+    }
+
+    @Benchmark
+    public String concatEmptyConstString() {
+        return "" + stringValue;
+    }
+
+    @Benchmark
     public String concatMethodConstString() {
         return "string".concat(stringValue);
     }