8247681: Improve bootstrapping of unary concatenations

Reviewed-by: jlaskey, psandoz
This commit is contained in:
Claes Redestad 2020-06-17 19:36:26 +02:00
parent 1d87958ead
commit 34c79640e7
6 changed files with 149 additions and 19 deletions

View file

@ -426,6 +426,22 @@ final class StringConcatHelper {
return newString(buf, indexCoder);
}
/**
* 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

View file

@ -447,9 +447,18 @@ public final class StringConcatFactory {
*/
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 @@ public final class StringConcatFactory {
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 @@ public final class StringConcatFactory {
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;

View file

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

View file

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

View file

@ -28,7 +28,7 @@ import java.util.concurrent.Callable;
/**
* @test
* @summary Test input invariants for StringConcatFactory
* @bug 8246152
* @bug 8246152 8247681
*
* @compile StringConcatFactoryInvariants.java
*
@ -46,6 +46,7 @@ public class StringConcatFactoryInvariants {
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 @@ public class StringConcatFactoryInvariants {
// 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 @@ public class StringConcatFactoryInvariants {
}
}
// 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));

View file

@ -24,11 +24,14 @@ package org.openjdk.bench.java.lang;
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 @@ import java.util.concurrent.TimeUnit;
@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")
@ -73,6 +79,16 @@ public class StringConcat {
return emptyString + stringValue;
}
@Benchmark
public String concatEmptyConstInt() {
return "" + intValue;
}
@Benchmark
public String concatEmptyConstString() {
return "" + stringValue;
}
@Benchmark
public String concatMethodConstString() {
return "string".concat(stringValue);