OpenJDK / jdk / jdk
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); }